@lido-nestjs/execution 1.10.1 → 1.11.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.
@@ -2,8 +2,20 @@ export declare const nonRetryableErrors: (string | number)[];
2
2
  export declare type ErrorWithCode = Error & {
3
3
  code: number | string;
4
4
  };
5
- export declare type ServerError = ErrorWithCode & {
5
+ export declare type HasErrorProperty = {
6
+ error: object;
7
+ };
8
+ export declare type HasServerErrorProperty = {
6
9
  serverError: object;
7
10
  };
8
11
  export declare const isErrorHasCode: (error: unknown) => error is ErrorWithCode;
9
- export declare const isCallExceptionServerError: (error: ErrorWithCode) => error is ServerError;
12
+ export declare const hasErrorProperty: (error: unknown) => error is HasErrorProperty;
13
+ export declare const hasServerErrorProperty: (error: unknown) => error is HasServerErrorProperty;
14
+ /**
15
+ * Detect that ethers error is a server error.
16
+ *
17
+ * Note: Ethers v5 error management is not very clean,
18
+ * and ethers error can be nested.
19
+ *
20
+ */
21
+ export declare const isEthersServerError: (error: unknown) => boolean;
@@ -69,11 +69,30 @@ const isErrorHasCode = (error) => {
69
69
  return (error instanceof Error &&
70
70
  Object.prototype.hasOwnProperty.call(error, 'code'));
71
71
  };
72
- const isCallExceptionServerError = (error) => {
73
- return (error.code === logger.ErrorCode.CALL_EXCEPTION &&
74
- Object.prototype.hasOwnProperty.call(error, 'serverError'));
72
+ const hasErrorProperty = (error) => {
73
+ return Object.prototype.hasOwnProperty.call(error, 'error');
74
+ };
75
+ const hasServerErrorProperty = (error) => {
76
+ return Object.prototype.hasOwnProperty.call(error, 'serverError');
77
+ };
78
+ /**
79
+ * Detect that ethers error is a server error.
80
+ *
81
+ * Note: Ethers v5 error management is not very clean,
82
+ * and ethers error can be nested.
83
+ *
84
+ */
85
+ const isEthersServerError = (error) => {
86
+ return (hasServerErrorProperty(error) || // nesting level 0
87
+ (hasErrorProperty(error) && hasServerErrorProperty(error.error)) || // nesting level 1
88
+ (hasErrorProperty(error) &&
89
+ hasErrorProperty(error.error) &&
90
+ hasServerErrorProperty(error.error.error)) // nesting level 2
91
+ );
75
92
  };
76
93
 
77
- exports.isCallExceptionServerError = isCallExceptionServerError;
94
+ exports.hasErrorProperty = hasErrorProperty;
95
+ exports.hasServerErrorProperty = hasServerErrorProperty;
78
96
  exports.isErrorHasCode = isErrorHasCode;
97
+ exports.isEthersServerError = isEthersServerError;
79
98
  exports.nonRetryableErrors = nonRetryableErrors;
@@ -1,7 +1,7 @@
1
1
  export declare class AllProvidersFailedError extends Error {
2
2
  name: string;
3
- message: string;
4
3
  code: number;
5
- originalError: Error | unknown;
4
+ cause: Error | unknown;
5
+ get originalError(): Error | unknown;
6
6
  constructor(message: string);
7
7
  }
@@ -4,10 +4,14 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  class AllProvidersFailedError extends Error {
6
6
  constructor(message) {
7
- super('');
7
+ super(message);
8
8
  this.name = 'AllProvidersFailedError';
9
9
  this.code = 0;
10
- this.message = message;
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
+ }
12
+ // for backward-compatibility
13
+ get originalError() {
14
+ return this.cause;
11
15
  }
12
16
  }
13
17
 
@@ -0,0 +1,4 @@
1
+ export declare enum ErrorCode {
2
+ UNEXPECTED_BATCH_RESULT = "UNEXPECTED_BATCH_RESULT",
3
+ PARTIAL_BATCH_RESULT = "PARTIAL_BATCH_RESULT"
4
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ exports.ErrorCode = void 0;
6
+ (function (ErrorCode) {
7
+ ErrorCode["UNEXPECTED_BATCH_RESULT"] = "UNEXPECTED_BATCH_RESULT";
8
+ ErrorCode["PARTIAL_BATCH_RESULT"] = "PARTIAL_BATCH_RESULT";
9
+ })(exports.ErrorCode || (exports.ErrorCode = {}));
@@ -1,7 +1,6 @@
1
1
  export declare class FetchError extends Error {
2
2
  name: string;
3
- message: string;
4
- code: number;
3
+ code: string | number;
5
4
  data: unknown;
6
5
  constructor(message: string);
7
6
  }
@@ -4,10 +4,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  class FetchError extends Error {
6
6
  constructor(message) {
7
- super('');
7
+ super(message);
8
8
  this.name = 'FetchError';
9
9
  this.code = 0;
10
- this.message = message;
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
11
  }
12
12
  }
13
13
 
@@ -0,0 +1,3 @@
1
+ export * from './all-providers-failed.error';
2
+ export * from './fetch.error';
3
+ export * from './no-new-blocks-while-polling.error';
@@ -1,6 +1,5 @@
1
1
  export declare class NoNewBlocksWhilePollingError extends Error {
2
2
  name: string;
3
- message: string;
4
3
  latestObservedBlockNumber: number;
5
4
  constructor(message: string, latestObservedBlockNumber: number);
6
5
  }
@@ -4,9 +4,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  class NoNewBlocksWhilePollingError extends Error {
6
6
  constructor(message, latestObservedBlockNumber) {
7
- super('');
7
+ super(message);
8
8
  this.name = 'NoNewBlocksWhilePollingError';
9
- this.message = message;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
10
  this.latestObservedBlockNumber = latestObservedBlockNumber;
11
11
  }
12
12
  }
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export * from './interfaces/simple-fallback-provider-config';
8
8
  export * from './interfaces/module.options';
9
9
  export * from './interfaces/non-empty-array';
10
10
  export * from './ethers/fee-history';
11
+ export * from './error';
package/dist/index.js CHANGED
@@ -8,6 +8,9 @@ var queue = require('./common/queue.js');
8
8
  var fallbackProvider_module = require('./fallback-provider.module.js');
9
9
  var batchProvider_module = require('./batch-provider.module.js');
10
10
  var feeHistory = require('./ethers/fee-history.js');
11
+ var allProvidersFailed_error = require('./error/all-providers-failed.error.js');
12
+ var fetch_error = require('./error/fetch.error.js');
13
+ var noNewBlocksWhilePolling_error = require('./error/no-new-blocks-while-polling.error.js');
11
14
 
12
15
 
13
16
 
@@ -31,3 +34,6 @@ Object.defineProperty(exports, 'BatchProviderModule', {
31
34
  exports.MAX_BLOCKCOUNT = feeHistory.MAX_BLOCKCOUNT;
32
35
  exports.MIN_BLOCKCOUNT = feeHistory.MIN_BLOCKCOUNT;
33
36
  exports.getFeeHistory = feeHistory.getFeeHistory;
37
+ exports.AllProvidersFailedError = allProvidersFailed_error.AllProvidersFailedError;
38
+ exports.FetchError = fetch_error.FetchError;
39
+ exports.NoNewBlocksWhilePollingError = noNewBlocksWhilePolling_error.NoNewBlocksWhilePollingError;
@@ -13,6 +13,7 @@ var promiseLimit = require('../common/promise-limit.js');
13
13
  var formatterWithEip1898 = require('../ethers/formatter-with-eip1898.js');
14
14
  var middleware = require('@lido-nestjs/middleware');
15
15
  var feeHistory = require('../ethers/fee-history.js');
16
+ var errorCodes = require('../error/codes/error-codes.js');
16
17
 
17
18
  exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extends providers.JsonRpcProvider {
18
19
  constructor(url, network, requestPolicy, fetchMiddlewares = []) {
@@ -56,12 +57,24 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
56
57
  });
57
58
  })
58
59
  .then((batchResult) => {
60
+ var _a;
59
61
  this.emit('debug', {
60
62
  action: 'response',
61
63
  request: properties.deepCopy(batchRequest),
62
64
  response: properties.deepCopy(batchResult),
63
65
  provider: this,
64
66
  });
67
+ if (!Array.isArray(batchResult)) {
68
+ const errMessage = 'Unexpected batch result.';
69
+ const jsonRpcErrorMessage = (_a = batchResult.error) === null || _a === void 0 ? void 0 : _a.message;
70
+ const detailedMessage = jsonRpcErrorMessage
71
+ ? ` Possible reason: "${jsonRpcErrorMessage}".`
72
+ : '';
73
+ const error = new fetch_error.FetchError(errMessage + detailedMessage);
74
+ error.code = errorCodes.ErrorCode.UNEXPECTED_BATCH_RESULT;
75
+ error.data = batchResult.error;
76
+ throw error;
77
+ }
65
78
  const resultMap = batchResult.reduce((resultMap, payload) => {
66
79
  resultMap[payload.id] = payload;
67
80
  return resultMap;
@@ -70,7 +83,13 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
70
83
  // on whether it was a success or error
71
84
  batch.forEach((inflightRequest) => {
72
85
  const payload = resultMap[inflightRequest.request.id];
73
- if (payload.error) {
86
+ if (!payload) {
87
+ const error = new fetch_error.FetchError(`Partial payload batch result. Response ${inflightRequest.request.id} not found`);
88
+ error.code = errorCodes.ErrorCode.PARTIAL_BATCH_RESULT;
89
+ error.data = batchResult;
90
+ inflightRequest.reject(error);
91
+ }
92
+ else if (payload.error) {
74
93
  const error = new fetch_error.FetchError(payload.error.message);
75
94
  error.code = payload.error.code;
76
95
  error.data = payload.error.data;
@@ -34,6 +34,7 @@ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
34
34
  protected detectNetworkFirstRun: boolean;
35
35
  protected resetTimer: ReturnType<typeof setTimeout> | null;
36
36
  protected lastPerformError: Error | null | unknown;
37
+ protected lastError: Error | null | unknown;
37
38
  constructor(config: SimpleFallbackProviderConfig, logger: LoggerService);
38
39
  static _formatter: Formatter | null;
39
40
  static getFormatter(): Formatter;
@@ -41,11 +42,12 @@ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
41
42
  getFeeHistory(blockCount: number, newestBlock?: string | null | number, rewardPercentiles?: number[]): Promise<FeeHistory>;
42
43
  protected get provider(): FallbackProvider;
43
44
  protected switchToNextProvider(): void;
44
- protected errorShouldBeReThrown(error: Error | unknown): boolean;
45
+ protected isNonRetryableError(error: Error | unknown): boolean;
45
46
  perform(method: string, params: {
46
47
  [name: string]: unknown;
47
48
  }): Promise<unknown>;
48
49
  detectNetwork(): Promise<Network>;
49
50
  protected resetFallbacks(): void;
50
51
  protected networksEqual(networkA: Network, networkB: Network): boolean;
52
+ get activeProviderIndex(): number;
51
53
  }
@@ -19,7 +19,9 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
19
19
  super(config.network);
20
20
  this.detectNetworkFirstRun = true;
21
21
  this.resetTimer = null;
22
- this.lastPerformError = null;
22
+ // it is crucial not to mix these two errors
23
+ this.lastPerformError = null; // last error for 'perform' operations, is batch-oriented
24
+ this.lastError = null; // last error for whole provider
23
25
  this.config = Object.assign({ maxRetries: 3, minBackoffMs: 500, maxBackoffMs: 5000, logRetries: true, resetIntervalMs: 10000, maxTimeWithoutNewBlocksMs: 60000 }, config);
24
26
  this.logger = logger;
25
27
  const conns = config.urls.filter((url) => {
@@ -103,18 +105,17 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
103
105
  this.activeFallbackProviderIndex++;
104
106
  this.logger.log(`Switched to next provider for execution layer`);
105
107
  }
106
- errorShouldBeReThrown(error) {
107
- return (errors.isErrorHasCode(error) &&
108
- errors.nonRetryableErrors.includes(error.code) &&
109
- !errors.isCallExceptionServerError(error));
108
+ isNonRetryableError(error) {
109
+ return (!errors.isEthersServerError(error) &&
110
+ errors.isErrorHasCode(error) &&
111
+ errors.nonRetryableErrors.includes(error.code));
110
112
  }
111
113
  async perform(method, params) {
112
- const retry = retrier.retrier(this.logger, this.config.maxRetries, this.config.minBackoffMs, this.config.maxBackoffMs, this.config.logRetries, (e) => this.errorShouldBeReThrown(e));
114
+ const retry = retrier.retrier(this.logger, this.config.maxRetries, this.config.minBackoffMs, this.config.maxBackoffMs, this.config.logRetries, (e) => this.isNonRetryableError(e));
113
115
  let attempt = 0;
114
116
  // will perform maximum `this.config.maxRetries` retries for fetching data with single provider
115
117
  // after failure will switch to next provider
116
118
  // maximum number of switching is limited to total fallback provider count
117
- let lastError;
118
119
  while (attempt < this.fallbackProviders.length) {
119
120
  try {
120
121
  attempt++;
@@ -123,11 +124,12 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
123
124
  return await retry(() => this.provider.provider.perform(method, params));
124
125
  }
125
126
  catch (e) {
126
- if (this.errorShouldBeReThrown(e)) {
127
+ this.lastError = e;
128
+ // checking that error should not be retried on another provider
129
+ if (this.isNonRetryableError(e)) {
127
130
  throw e;
128
131
  }
129
132
  this.logger.error('Error while doing ETH1 RPC request. Will try to switch to another provider');
130
- lastError = e;
131
133
  this.logger.error(e);
132
134
  // This check is needed to avoid multiple `switchToNextProvider` calls when doing one JSON-RPC batch.
133
135
  // This can happen when multiple N calls to `perform` are batched in one JSON-RPC request and
@@ -141,7 +143,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
141
143
  }
142
144
  }
143
145
  const allProvidersFailedError = new allProvidersFailed_error.AllProvidersFailedError('All attempts to do ETH1 RPC request failed');
144
- allProvidersFailedError.originalError = lastError;
146
+ allProvidersFailedError.cause = this.lastError;
145
147
  throw allProvidersFailedError;
146
148
  }
147
149
  async detectNetwork() {
@@ -156,6 +158,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
156
158
  else {
157
159
  this.fallbackProviders[index].network = null;
158
160
  this.fallbackProviders[index].unreachable = true;
161
+ this.lastError = result.reason;
159
162
  }
160
163
  });
161
164
  let previousNetwork = null;
@@ -187,7 +190,9 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
187
190
  }
188
191
  });
189
192
  if (!previousNetwork) {
190
- throw new Error('All fallback endpoints are unreachable or all fallback networks differ between each other');
193
+ const error = new allProvidersFailed_error.AllProvidersFailedError('All fallback endpoints are unreachable or all fallback networks differ between each other');
194
+ error.cause = this.lastError;
195
+ throw error;
191
196
  }
192
197
  if (this.detectNetworkFirstRun) {
193
198
  this.detectNetworkFirstRun = false;
@@ -215,6 +220,9 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
215
220
  networksEqual(networkA, networkB) {
216
221
  return networks.networksEqual(networkA, networkB);
217
222
  }
223
+ get activeProviderIndex() {
224
+ return this.activeFallbackProviderIndex;
225
+ }
218
226
  };
219
227
  exports.SimpleFallbackJsonRpcBatchProvider._formatter = null;
220
228
  exports.SimpleFallbackJsonRpcBatchProvider = tslib.__decorate([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lido-nestjs/execution",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "MIT",