@lido-nestjs/execution 1.14.2 → 1.16.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,4 +1,5 @@
1
1
  export declare enum ErrorCode {
2
2
  UNEXPECTED_BATCH_RESULT = "UNEXPECTED_BATCH_RESULT",
3
- PARTIAL_BATCH_RESULT = "PARTIAL_BATCH_RESULT"
3
+ PARTIAL_BATCH_RESULT = "PARTIAL_BATCH_RESULT",
4
+ REQUEST_TIMEOUT = "REQUEST_TIMEOUT"
4
5
  }
@@ -6,4 +6,5 @@ exports.ErrorCode = void 0;
6
6
  (function (ErrorCode) {
7
7
  ErrorCode["UNEXPECTED_BATCH_RESULT"] = "UNEXPECTED_BATCH_RESULT";
8
8
  ErrorCode["PARTIAL_BATCH_RESULT"] = "PARTIAL_BATCH_RESULT";
9
+ ErrorCode["REQUEST_TIMEOUT"] = "REQUEST_TIMEOUT";
9
10
  })(exports.ErrorCode || (exports.ErrorCode = {}));
@@ -1,3 +1,4 @@
1
1
  export * from './all-providers-failed.error';
2
2
  export * from './fetch.error';
3
3
  export * from './no-new-blocks-while-polling.error';
4
+ export * from './request-timeout.error';
@@ -0,0 +1,5 @@
1
+ export declare class RequestTimeoutError extends Error {
2
+ name: string;
3
+ timeoutMs: number;
4
+ constructor(message: string, timeoutMs: number);
5
+ }
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ class RequestTimeoutError extends Error {
6
+ constructor(message, timeoutMs) {
7
+ super(message);
8
+ this.name = 'RequestTimeoutError';
9
+ this.timeoutMs = timeoutMs;
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
+ }
12
+ }
13
+
14
+ exports.RequestTimeoutError = RequestTimeoutError;
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ var feeHistory = require('./ethers/fee-history.js');
11
11
  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
+ var requestTimeout_error = require('./error/request-timeout.error.js');
14
15
 
15
16
 
16
17
 
@@ -37,3 +38,4 @@ exports.getFeeHistory = feeHistory.getFeeHistory;
37
38
  exports.AllProvidersFailedError = allProvidersFailed_error.AllProvidersFailedError;
38
39
  exports.FetchError = fetch_error.FetchError;
39
40
  exports.NoNewBlocksWhilePollingError = noNewBlocksWhilePolling_error.NoNewBlocksWhilePollingError;
41
+ exports.RequestTimeoutError = requestTimeout_error.RequestTimeoutError;
@@ -14,4 +14,6 @@ export interface SimpleFallbackProviderConfig {
14
14
  resetIntervalMs?: number;
15
15
  fetchMiddlewares?: MiddlewareCallback<Promise<any>>[];
16
16
  maxTimeWithoutNewBlocksMs?: number;
17
+ requestTimeoutMs?: number;
18
+ instanceLabel?: string;
17
19
  }
@@ -46,12 +46,14 @@ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
46
46
  constructor(config: SimpleFallbackProviderConfig, logger: LoggerService);
47
47
  static _formatter: Formatter | null;
48
48
  static getFormatter(): Formatter;
49
+ protected formatLog(message: string, providerIndex?: number): string;
49
50
  on(eventName: EventType, listener: Listener): this;
50
51
  getFeeHistory(blockCount: number, newestBlock?: string | null | number, rewardPercentiles?: number[]): Promise<FeeHistory>;
51
52
  getDebugTraceBlockByHash(blockHash: string, traceConfig: Partial<TraceConfig>): Promise<TraceResult[]>;
52
53
  protected get provider(): FallbackProvider;
53
54
  protected switchToNextProvider(): void;
54
55
  protected isNonRetryableError(error: Error | unknown): boolean;
56
+ protected withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T>;
55
57
  perform(method: string, params: {
56
58
  [name: string]: unknown;
57
59
  }): Promise<unknown>;
@@ -12,12 +12,14 @@ var networks = require('../common/networks.js');
12
12
  var noNewBlocksWhilePolling_error = require('../error/no-new-blocks-while-polling.error.js');
13
13
  var errors = require('../common/errors.js');
14
14
  var allProvidersFailed_error = require('../error/all-providers-failed.error.js');
15
+ var requestTimeout_error = require('../error/request-timeout.error.js');
15
16
  var feeHistory = require('../ethers/fee-history.js');
16
17
  var debugTraceBlockByHash = require('../ethers/debug-trace-block-by-hash.js');
17
18
  var events = require('events');
18
19
 
19
20
  exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchProvider extends providers.BaseProvider {
20
21
  constructor(config, logger) {
22
+ var _a;
21
23
  super(config.network);
22
24
  this.detectNetworkFirstRun = true;
23
25
  this.resetTimer = null;
@@ -54,6 +56,22 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
54
56
  };
55
57
  });
56
58
  this.activeFallbackProviderIndex = 0;
59
+ // Log initialization info
60
+ const configInfo = {
61
+ providers: conns.length,
62
+ network: this.config.network,
63
+ requestPolicy: this.config.requestPolicy,
64
+ maxRetries: this.config.maxRetries,
65
+ minBackoffMs: this.config.minBackoffMs,
66
+ maxBackoffMs: this.config.maxBackoffMs,
67
+ logRetries: this.config.logRetries,
68
+ resetIntervalMs: this.config.resetIntervalMs,
69
+ fetchMiddlewares: ((_a = this.config.fetchMiddlewares) === null || _a === void 0 ? void 0 : _a.length) || 0,
70
+ maxTimeWithoutNewBlocksMs: this.config.maxTimeWithoutNewBlocksMs,
71
+ requestTimeoutMs: this.config.requestTimeoutMs || 'disabled',
72
+ instanceLabel: this.config.instanceLabel || 'none',
73
+ };
74
+ this.logger.log(this.formatLog(`Initialized SimpleFallbackJsonRpcBatchProvider: ${JSON.stringify(configInfo)}`));
57
75
  }
58
76
  static getFormatter() {
59
77
  if (this._formatter == null) {
@@ -61,6 +79,19 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
61
79
  }
62
80
  return this._formatter;
63
81
  }
82
+ formatLog(message, providerIndex) {
83
+ const parts = [];
84
+ if (this.config.instanceLabel) {
85
+ parts.push(`[${this.config.instanceLabel}]`);
86
+ }
87
+ if (providerIndex !== undefined) {
88
+ parts.push(`[provider:${providerIndex}]`);
89
+ }
90
+ if (parts.length > 0) {
91
+ return `${parts.join('')} ${message}`;
92
+ }
93
+ return message;
94
+ }
64
95
  on(eventName, listener) {
65
96
  let dieTimer = null;
66
97
  const startDieTimer = (latestObservedBlockNumber) => {
@@ -109,17 +140,29 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
109
140
  }
110
141
  switchToNextProvider() {
111
142
  if (this.fallbackProviders.length === 1) {
112
- this.logger.warn('Will not switch to next provider. No valid backup provider provided.');
143
+ this.logger.warn(this.formatLog('Will not switch to next provider. No valid backup provider provided.'));
113
144
  return;
114
145
  }
115
- this.activeFallbackProviderIndex++;
116
- this.logger.log(`Switched to next provider for execution layer`);
146
+ const oldIndex = this.activeFallbackProviderIndex;
147
+ this.activeFallbackProviderIndex =
148
+ (this.activeFallbackProviderIndex + 1) % this.fallbackProviders.length;
149
+ this.logger.log(this.formatLog(`Switched provider: [${oldIndex}] -> [${this.activeFallbackProviderIndex}] (total: ${this.fallbackProviders.length})`));
117
150
  }
118
151
  isNonRetryableError(error) {
119
152
  return (!errors.isEthersServerError(error) &&
120
153
  errors.isErrorHasCode(error) &&
121
154
  errors.nonRetryableErrors.includes(error.code));
122
155
  }
156
+ withTimeout(promise, timeoutMs) {
157
+ return Promise.race([
158
+ promise,
159
+ new Promise((_, reject) => {
160
+ setTimeout(() => {
161
+ reject(new requestTimeout_error.RequestTimeoutError(`Request timeout after ${timeoutMs}ms`, timeoutMs));
162
+ }, timeoutMs);
163
+ }),
164
+ ]);
165
+ }
123
166
  async perform(method, params) {
124
167
  const retry = retrier.retrier(this.logger, this.config.maxRetries, this.config.minBackoffMs, this.config.maxBackoffMs, this.config.logRetries, (e) => this.isNonRetryableError(e));
125
168
  let attempt = 0;
@@ -130,9 +173,11 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
130
173
  try {
131
174
  let performRetryAttempt = 0;
132
175
  attempt++;
176
+ // Log which provider we're attempting to use
177
+ this.logger.log(this.formatLog(`Attempting request (attempt ${attempt}/${this.fallbackProviders.length})`, this.activeFallbackProviderIndex));
133
178
  // awaiting is extremely important here
134
179
  // without it, the error will not be caught in current try-catch scope
135
- return await retry(() => {
180
+ const result = await retry(() => {
136
181
  const provider = this.provider;
137
182
  const event = {
138
183
  action: 'fallback-provider:request',
@@ -144,8 +189,16 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
144
189
  };
145
190
  this._eventEmitter.emit('rpc', event);
146
191
  performRetryAttempt++;
147
- return provider.provider.perform(method, params);
192
+ const performPromise = provider.provider.perform(method, params);
193
+ // Apply timeout if configured
194
+ if (this.config.requestTimeoutMs) {
195
+ return this.withTimeout(performPromise, this.config.requestTimeoutMs);
196
+ }
197
+ return performPromise;
148
198
  });
199
+ // Log successful request
200
+ this.logger.log(this.formatLog(`Request successful after ${performRetryAttempt} retry attempt(s)`, this.activeFallbackProviderIndex));
201
+ return result;
149
202
  }
150
203
  catch (e) {
151
204
  this.lastError = e;
@@ -157,10 +210,22 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
157
210
  error: e,
158
211
  };
159
212
  this._eventEmitter.emit('rpc', event);
213
+ // Log context (label + provider index) synchronously before error object
214
+ // to ensure proper ordering in async logging systems
215
+ this.logger.error(this.formatLog(`Non-retryable error occurred`, this.activeFallbackProviderIndex));
216
+ this.logger.error(e);
160
217
  throw e;
161
218
  }
162
- this.logger.error('Error while doing ETH1 RPC request. Will try to switch to another provider');
163
- this.logger.error(e);
219
+ // Log context (label + provider index) synchronously before error object
220
+ // to ensure proper ordering in async logging systems
221
+ if (e instanceof requestTimeout_error.RequestTimeoutError) {
222
+ this.logger.error(this.formatLog(`Request timeout after ${e.timeoutMs}ms. Will switch to next provider.`, this.activeFallbackProviderIndex));
223
+ this.logger.error(e);
224
+ }
225
+ else {
226
+ this.logger.error(this.formatLog(`Error occurred. Will switch to next provider.`, this.activeFallbackProviderIndex));
227
+ this.logger.error(e);
228
+ }
164
229
  // This check is needed to avoid multiple `switchToNextProvider` calls when doing one JSON-RPC batch.
165
230
  // This can happen when multiple N calls to `perform` are batched in one JSON-RPC request and
166
231
  // that request fails and throws `Error`. This `Error` is bubbled N times to corresponding `perform` calls.
@@ -218,7 +283,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
218
283
  if (this.detectNetworkFirstRun) {
219
284
  throw new Error(`Fallback provider [${index}] network is different to other provider's networks`);
220
285
  }
221
- this.logger.warn(`Fallback provider [${index}] network is different to other provider's networks`);
286
+ this.logger.warn(this.formatLog(`Fallback provider [${index}] network is different to other provider's networks`));
222
287
  }
223
288
  }
224
289
  else {
@@ -245,7 +310,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
245
310
  if (this.resetTimer) {
246
311
  clearTimeout(this.resetTimer);
247
312
  }
248
- this.fallbackProviders.forEach((fallbackProvider, index) => {
313
+ this.fallbackProviders.forEach((_, index) => {
249
314
  var _a;
250
315
  if (!((_a = this.fallbackProviders[index].network) === null || _a === void 0 ? void 0 : _a.chainId)) {
251
316
  this.fallbackProviders[index].unreachable = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lido-nestjs/execution",
3
- "version": "1.14.2",
3
+ "version": "1.16.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "MIT",