@lido-nestjs/execution 1.0.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.
Files changed (33) hide show
  1. package/LICENSE.txt +21 -0
  2. package/README.md +51 -0
  3. package/dist/common/networks.d.ts +5 -0
  4. package/dist/common/networks.js +18 -0
  5. package/dist/common/promise-limit.d.ts +7 -0
  6. package/dist/common/promise-limit.js +65 -0
  7. package/dist/common/queue.d.ts +8 -0
  8. package/dist/common/queue.js +35 -0
  9. package/dist/common/retrier.d.ts +2 -0
  10. package/dist/common/retrier.js +29 -0
  11. package/dist/common/sleep.d.ts +1 -0
  12. package/dist/common/sleep.js +7 -0
  13. package/dist/constants/constants.d.ts +5 -0
  14. package/dist/constants/constants.js +17 -0
  15. package/dist/error/fetch.error.d.ts +7 -0
  16. package/dist/error/fetch.error.js +14 -0
  17. package/dist/ethers/block-tag.d.ts +6 -0
  18. package/dist/ethers/formatter-with-eip1898.d.ts +8 -0
  19. package/dist/ethers/formatter-with-eip1898.js +23 -0
  20. package/dist/execution.module.d.ts +8 -0
  21. package/dist/execution.module.js +85 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +24 -0
  24. package/dist/interfaces/fallback-provider.d.ts +7 -0
  25. package/dist/interfaces/module.options.d.ts +8 -0
  26. package/dist/interfaces/networkish.d.ts +7 -0
  27. package/dist/interfaces/non-empty-array.d.ts +3 -0
  28. package/dist/interfaces/simple-fallback-provider-config.d.ts +15 -0
  29. package/dist/provider/extended-json-rpc-batch-provider.d.ts +77 -0
  30. package/dist/provider/extended-json-rpc-batch-provider.js +149 -0
  31. package/dist/provider/simple-fallback-json-rpc-batch-provider.d.ts +39 -0
  32. package/dist/provider/simple-fallback-json-rpc-batch-provider.js +152 -0
  33. package/package.json +58 -0
package/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Lido
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Execution layer (Eth1 RPC Provider)
2
+
3
+ NestJS Logger for Lido Finance projects.
4
+ Part of [Lido NestJS Modules](https://github.com/lidofinance/lido-nestjs-modules/#readme)
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ yarn add @lido-nestjs/execution
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Basic usage
15
+
16
+ ```ts
17
+ // Import
18
+ import { Injectable, Module } from '@nestjs/common';
19
+ import { ExecutionModule } from '@lido-nestjs/execution';
20
+ import { MyService } from './my.service';
21
+
22
+ @Module({
23
+ imports: [
24
+ LoggerModule.forRoot({}),
25
+ ExecutionModule.forRoot({
26
+ imports: [],
27
+ urls: ['http://localhost:8545', 'http://fallback:8545'],
28
+ network: 1,
29
+ }),
30
+ ],
31
+ providers: [MyService],
32
+ exports: [MyService],
33
+ })
34
+ export class MyModule {}
35
+
36
+ // Usage
37
+ import { SimpleFallbackJsonRpcBatchProvider } from '@lido-nestjs/execution';
38
+
39
+ @Injectable
40
+ export class MyService {
41
+ constructor(private provider: SimpleFallbackJsonRpcBatchProvider) {}
42
+
43
+ async doSomeWork() {
44
+ return await this.provider.getBlock(1000);
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Async usage
50
+
51
+ // TODO
@@ -0,0 +1,5 @@
1
+ import { Network } from '@ethersproject/networks';
2
+ import { Networkish } from '../interfaces/networkish';
3
+ export declare const networksEqual: (networkA: Network, networkB: Network) => boolean;
4
+ export declare const getNetworkChain: (networkish: Networkish) => number;
5
+ export declare const networksChainsEqual: (networkA: Network, networkB: Networkish) => boolean;
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const networksEqual = (networkA, networkB) => {
6
+ return (networkA.name === networkB.name &&
7
+ networkA.chainId === networkB.chainId &&
8
+ (networkA.ensAddress === networkB.ensAddress ||
9
+ (!networkA.ensAddress && !networkB.ensAddress)));
10
+ };
11
+ const getNetworkChain = (networkish) => typeof networkish === 'object' && networkish != null
12
+ ? networkish.chainId
13
+ : networkish;
14
+ const networksChainsEqual = (networkA, networkB) => networkA.chainId === getNetworkChain(networkB);
15
+
16
+ exports.getNetworkChain = getNetworkChain;
17
+ exports.networksChainsEqual = networksChainsEqual;
18
+ exports.networksEqual = networksEqual;
@@ -0,0 +1,7 @@
1
+ export interface LimitFunction {
2
+ readonly activeCount: number;
3
+ readonly pendingCount: number;
4
+ clearQueue: () => void;
5
+ <Arguments extends unknown[], ReturnType>(fn: (...args: Arguments) => PromiseLike<ReturnType> | ReturnType, ...args: Arguments): Promise<ReturnType>;
6
+ }
7
+ export default function pLimit(concurrency: number): LimitFunction;
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var queue = require('./queue.js');
6
+
7
+ /* eslint-disable @typescript-eslint/ban-types */
8
+ function pLimit(concurrency) {
9
+ if (!((Number.isInteger(concurrency) ||
10
+ concurrency === Number.POSITIVE_INFINITY) &&
11
+ concurrency > 0)) {
12
+ throw new TypeError('Expected `concurrency` to be positive integer');
13
+ }
14
+ const queue$1 = new queue.Queue();
15
+ let activeCount = 0;
16
+ const next = () => {
17
+ activeCount--;
18
+ if (queue$1.length > 0) {
19
+ const item = queue$1.dequeue();
20
+ item && item();
21
+ }
22
+ };
23
+ const run = async (fn, resolve, args) => {
24
+ activeCount++;
25
+ const result = (async () => fn(...args))();
26
+ resolve(result);
27
+ try {
28
+ await result;
29
+ }
30
+ catch (_a) {
31
+ // should not catch exceptions here
32
+ // exceptions should be caught in above handlers
33
+ }
34
+ next();
35
+ };
36
+ const enqueue = (fn, resolve, args) => {
37
+ queue$1.enqueue(run.bind(undefined, fn, resolve, args));
38
+ (async () => {
39
+ await Promise.resolve();
40
+ if (activeCount < concurrency && queue$1.length > 0) {
41
+ const item = queue$1.dequeue();
42
+ item && item();
43
+ }
44
+ })();
45
+ };
46
+ const generator = (fn, ...args) => new Promise((resolve) => {
47
+ enqueue(fn, resolve, args);
48
+ });
49
+ Object.defineProperties(generator, {
50
+ activeCount: {
51
+ get: () => activeCount,
52
+ },
53
+ pendingCount: {
54
+ get: () => queue$1.length,
55
+ },
56
+ clearQueue: {
57
+ value: () => {
58
+ queue$1.clear();
59
+ },
60
+ },
61
+ });
62
+ return generator;
63
+ }
64
+
65
+ exports["default"] = pLimit;
@@ -0,0 +1,8 @@
1
+ export declare class Queue<T> {
2
+ private _store;
3
+ enqueue(val: T): void;
4
+ dequeue(): T | undefined;
5
+ get length(): number;
6
+ clear(): void;
7
+ dequeueMultiple(batch: number): T[];
8
+ }
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ class Queue {
6
+ constructor() {
7
+ this._store = [];
8
+ }
9
+ enqueue(val) {
10
+ this._store.push(val);
11
+ }
12
+ dequeue() {
13
+ return this._store.shift();
14
+ }
15
+ get length() {
16
+ return this._store.length;
17
+ }
18
+ clear() {
19
+ this._store = [];
20
+ }
21
+ dequeueMultiple(batch) {
22
+ batch = (batch | 0) > 0 ? batch | 0 : 1;
23
+ const buffer = [];
24
+ for (let i = 0; i < batch; i++) {
25
+ const value = this.dequeue();
26
+ if (typeof value === 'undefined') {
27
+ break;
28
+ }
29
+ buffer.push(value);
30
+ }
31
+ return buffer;
32
+ }
33
+ }
34
+
35
+ exports.Queue = Queue;
@@ -0,0 +1,2 @@
1
+ import { LoggerService } from '@nestjs/common/services/logger.service';
2
+ export declare const retrier: (logger?: LoggerService | null | undefined, defaultMaxRetryCount?: number, defaultMinBackoffMs?: number, defaultMaxBackoffMs?: number, defaultLogWarning?: boolean) => <T extends unknown>(callback: () => T | Promise<T>, maxRetryCount?: number | undefined, minBackoffMs?: number | undefined, maxBackoffMs?: number | undefined, logWarning?: boolean | undefined) => Promise<T>;
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var sleep = require('./sleep.js');
6
+
7
+ const retrier = (logger, defaultMaxRetryCount = 3, defaultMinBackoffMs = 1000, defaultMaxBackoffMs = 60000, defaultLogWarning = false) => {
8
+ return async (callback, maxRetryCount, minBackoffMs, maxBackoffMs, logWarning) => {
9
+ maxRetryCount = maxRetryCount !== null && maxRetryCount !== void 0 ? maxRetryCount : defaultMaxRetryCount;
10
+ minBackoffMs = minBackoffMs !== null && minBackoffMs !== void 0 ? minBackoffMs : defaultMinBackoffMs;
11
+ maxBackoffMs = maxBackoffMs !== null && maxBackoffMs !== void 0 ? maxBackoffMs : defaultMaxBackoffMs;
12
+ logWarning = logWarning !== null && logWarning !== void 0 ? logWarning : defaultLogWarning;
13
+ try {
14
+ return await callback();
15
+ }
16
+ catch (err) {
17
+ if (logger && logWarning) {
18
+ logger.warn(err, 'Retrying after (%dms). Remaining retries [%d]', minBackoffMs, maxRetryCount);
19
+ }
20
+ if (maxRetryCount <= 1 || minBackoffMs >= maxBackoffMs) {
21
+ throw err;
22
+ }
23
+ await sleep.sleep(minBackoffMs);
24
+ return await retrier(logger)(callback, maxRetryCount - 1, minBackoffMs * 2, maxBackoffMs, logWarning);
25
+ }
26
+ };
27
+ };
28
+
29
+ exports.retrier = retrier;
@@ -0,0 +1 @@
1
+ export declare const sleep: (ms: number) => Promise<unknown>;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6
+
7
+ exports.sleep = sleep;
@@ -0,0 +1,5 @@
1
+ export declare const EXECUTION_MODULE_OPTIONS: unique symbol;
2
+ export declare enum Provider {
3
+ ExtendedJsonRpcBatchProvider = 0,
4
+ SimpleFallbackJsonRpcBatchProvider = 1
5
+ }
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const EXECUTION_MODULE_OPTIONS = Symbol('execution-module-options');
6
+ exports.Provider = void 0;
7
+ (function (Provider) {
8
+ Provider[Provider["ExtendedJsonRpcBatchProvider"] = 0] = "ExtendedJsonRpcBatchProvider";
9
+ Provider[Provider["SimpleFallbackJsonRpcBatchProvider"] = 1] = "SimpleFallbackJsonRpcBatchProvider";
10
+ })(exports.Provider || (exports.Provider = {}));
11
+ // export type Providers =
12
+ // [Provider.ExtendedJsonRpcBatchProvider, Provider.SimpleFallbackJsonRpcBatchProvider] |
13
+ // [Provider.SimpleFallbackJsonRpcBatchProvider, Provider.ExtendedJsonRpcBatchProvider] |
14
+ // [Provider.SimpleFallbackJsonRpcBatchProvider] |
15
+ // [Provider.ExtendedJsonRpcBatchProvider];
16
+
17
+ exports.EXECUTION_MODULE_OPTIONS = EXECUTION_MODULE_OPTIONS;
@@ -0,0 +1,7 @@
1
+ export declare class FetchError extends Error {
2
+ name: string;
3
+ message: string;
4
+ code: number;
5
+ data: unknown;
6
+ constructor(message: string);
7
+ }
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ class FetchError extends Error {
6
+ constructor(message) {
7
+ super('');
8
+ this.name = 'FetchError';
9
+ this.code = 0;
10
+ this.message = message;
11
+ }
12
+ }
13
+
14
+ exports.FetchError = FetchError;
@@ -0,0 +1,6 @@
1
+ export declare type BlockTag = string | number | {
2
+ blockNumber: string;
3
+ } | {
4
+ blockHash: string;
5
+ requireCanonical?: boolean;
6
+ };
@@ -0,0 +1,8 @@
1
+ import { Formatter } from '@ethersproject/providers';
2
+ export declare class FormatterWithEIP1898 extends Formatter {
3
+ /**
4
+ * blockTag formatter with EIP-1898 support
5
+ * https://eips.ethereum.org/EIPS/eip-1898
6
+ */
7
+ blockTag(blockTag: any): any;
8
+ }
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var providers = require('@ethersproject/providers');
6
+
7
+ /* eslint-disable @typescript-eslint/no-explicit-any */
8
+ class FormatterWithEIP1898 extends providers.Formatter {
9
+ /**
10
+ * blockTag formatter with EIP-1898 support
11
+ * https://eips.ethereum.org/EIPS/eip-1898
12
+ */
13
+ blockTag(blockTag) {
14
+ if (typeof blockTag === 'object' &&
15
+ blockTag != null &&
16
+ (blockTag.blockNumber || blockTag.blockHash)) {
17
+ return blockTag;
18
+ }
19
+ return super.blockTag(blockTag);
20
+ }
21
+ }
22
+
23
+ exports.FormatterWithEIP1898 = FormatterWithEIP1898;
@@ -0,0 +1,8 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { ExecutionModuleAsyncOptions, ExecutionModuleSyncOptions } from './interfaces/module.options';
3
+ export declare class ExecutionModule {
4
+ static forRoot(options: ExecutionModuleSyncOptions): DynamicModule;
5
+ static forFeature(options: ExecutionModuleSyncOptions): DynamicModule;
6
+ static forRootAsync(options: ExecutionModuleAsyncOptions): DynamicModule;
7
+ static forFeatureAsync(options: ExecutionModuleAsyncOptions): DynamicModule;
8
+ }
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var common = require('@nestjs/common');
7
+ var extendedJsonRpcBatchProvider = require('./provider/extended-json-rpc-batch-provider.js');
8
+ var constants = require('./constants/constants.js');
9
+ var simpleFallbackJsonRpcBatchProvider = require('./provider/simple-fallback-json-rpc-batch-provider.js');
10
+ var logger = require('@lido-nestjs/logger');
11
+
12
+ var ExecutionModule_1;
13
+ const getModuleProviders = (options) => {
14
+ return [
15
+ {
16
+ provide: simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider,
17
+ useFactory: (logger) => {
18
+ return new simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider(options, logger);
19
+ },
20
+ inject: [logger.LOGGER_PROVIDER],
21
+ },
22
+ {
23
+ provide: extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider,
24
+ useFactory: () => {
25
+ return new extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider(options.urls[0], undefined, // options.network,
26
+ options.requestPolicy, options.fetchMiddlewares);
27
+ },
28
+ },
29
+ ];
30
+ };
31
+ exports.ExecutionModule = ExecutionModule_1 = class ExecutionModule {
32
+ static forRoot(options) {
33
+ return Object.assign({ global: true }, this.forFeature(options));
34
+ }
35
+ static forFeature(options) {
36
+ return {
37
+ module: ExecutionModule_1,
38
+ imports: options.imports,
39
+ providers: getModuleProviders(options),
40
+ exports: [
41
+ simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider,
42
+ extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider,
43
+ ],
44
+ };
45
+ }
46
+ static forRootAsync(options) {
47
+ return Object.assign({ global: true }, this.forFeatureAsync(options));
48
+ }
49
+ static forFeatureAsync(options) {
50
+ return {
51
+ module: ExecutionModule_1,
52
+ imports: options.imports,
53
+ providers: [
54
+ {
55
+ provide: constants.EXECUTION_MODULE_OPTIONS,
56
+ useFactory: options.useFactory,
57
+ inject: options.inject,
58
+ },
59
+ {
60
+ provide: simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider,
61
+ useFactory: (logger, options) => {
62
+ return new simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider(options, logger);
63
+ },
64
+ inject: [logger.LOGGER_PROVIDER, constants.EXECUTION_MODULE_OPTIONS],
65
+ },
66
+ {
67
+ provide: extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider,
68
+ useFactory: (options) => {
69
+ return new extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider(options.urls[0], undefined, // options.network,
70
+ options.requestPolicy);
71
+ },
72
+ inject: [constants.EXECUTION_MODULE_OPTIONS],
73
+ },
74
+ ...(options.providers || []),
75
+ ],
76
+ exports: [
77
+ simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider,
78
+ extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider,
79
+ ],
80
+ };
81
+ }
82
+ };
83
+ exports.ExecutionModule = ExecutionModule_1 = tslib.__decorate([
84
+ common.Module({})
85
+ ], exports.ExecutionModule);
@@ -0,0 +1,8 @@
1
+ export * from './provider/extended-json-rpc-batch-provider';
2
+ export * from './provider/simple-fallback-json-rpc-batch-provider';
3
+ export * from './common/queue';
4
+ export * from './execution.module';
5
+ export * from './interfaces/fallback-provider';
6
+ export * from './interfaces/simple-fallback-provider-config';
7
+ export { ExecutionModuleAsyncOptions } from './interfaces/module.options';
8
+ export { ExecutionModuleSyncOptions } from './interfaces/module.options';
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var extendedJsonRpcBatchProvider = require('./provider/extended-json-rpc-batch-provider.js');
6
+ var simpleFallbackJsonRpcBatchProvider = require('./provider/simple-fallback-json-rpc-batch-provider.js');
7
+ var queue = require('./common/queue.js');
8
+ var execution_module = require('./execution.module.js');
9
+
10
+
11
+
12
+ Object.defineProperty(exports, 'ExtendedJsonRpcBatchProvider', {
13
+ enumerable: true,
14
+ get: function () { return extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider; }
15
+ });
16
+ Object.defineProperty(exports, 'SimpleFallbackJsonRpcBatchProvider', {
17
+ enumerable: true,
18
+ get: function () { return simpleFallbackJsonRpcBatchProvider.SimpleFallbackJsonRpcBatchProvider; }
19
+ });
20
+ exports.Queue = queue.Queue;
21
+ Object.defineProperty(exports, 'ExecutionModule', {
22
+ enumerable: true,
23
+ get: function () { return execution_module.ExecutionModule; }
24
+ });
@@ -0,0 +1,7 @@
1
+ import { ExtendedJsonRpcBatchProvider } from '../provider/extended-json-rpc-batch-provider';
2
+ import { Network } from '@ethersproject/networks';
3
+ export interface FallbackProvider {
4
+ provider: ExtendedJsonRpcBatchProvider;
5
+ network: Network | null;
6
+ index: number;
7
+ }
@@ -0,0 +1,8 @@
1
+ import { ModuleMetadata } from '@nestjs/common';
2
+ import { SimpleFallbackProviderConfig } from './simple-fallback-provider-config';
3
+ export interface ExecutionModuleSyncOptions extends Pick<ModuleMetadata, 'imports'>, SimpleFallbackProviderConfig {
4
+ }
5
+ export interface ExecutionModuleAsyncOptions extends Pick<ModuleMetadata, 'imports' | 'providers'> {
6
+ useFactory: (...args: any[]) => Promise<ExecutionModuleSyncOptions> | ExecutionModuleSyncOptions;
7
+ inject: any[];
8
+ }
@@ -0,0 +1,7 @@
1
+ export declare type ChainId = number;
2
+ export declare type NetworkInfo = {
3
+ name: string;
4
+ chainId: ChainId;
5
+ ensAddress?: string;
6
+ };
7
+ export declare type Networkish = NetworkInfo | ChainId;
@@ -0,0 +1,3 @@
1
+ export declare type NonEmptyArray<T> = T[] & {
2
+ 0: T;
3
+ };
@@ -0,0 +1,15 @@
1
+ import { RequestPolicy } from '../provider/extended-json-rpc-batch-provider';
2
+ import { ConnectionInfo } from '@ethersproject/web';
3
+ import { Networkish } from './networkish';
4
+ import { NonEmptyArray } from './non-empty-array';
5
+ import { MiddlewareCallback } from '@lido-nestjs/middleware';
6
+ export interface SimpleFallbackProviderConfig {
7
+ urls: NonEmptyArray<ConnectionInfo | string>;
8
+ network: Networkish;
9
+ requestPolicy?: RequestPolicy;
10
+ maxRetries?: number;
11
+ minBackoffMs?: number;
12
+ maxBackoffMs?: number;
13
+ logRetries?: boolean;
14
+ fetchMiddlewares?: MiddlewareCallback<Promise<any>>[];
15
+ }
@@ -0,0 +1,77 @@
1
+ /// <reference types="node" />
2
+ import { Deferrable } from '@ethersproject/properties';
3
+ import { ConnectionInfo, FetchJsonResponse } from '@ethersproject/web';
4
+ import { Formatter, JsonRpcProvider } from '@ethersproject/providers';
5
+ import { Network, Networkish } from '@ethersproject/networks';
6
+ import { Queue } from '../common/queue';
7
+ import { LimitFunction } from '../common/promise-limit';
8
+ import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
9
+ import { BlockTag } from '../ethers/block-tag';
10
+ import { TransactionRequest } from '@ethersproject/abstract-provider/src.ts/index';
11
+ import { MiddlewareCallback, MiddlewareService } from '@lido-nestjs/middleware';
12
+ export interface RequestPolicy {
13
+ jsonRpcMaxBatchSize: number;
14
+ maxConcurrentRequests: number;
15
+ batchAggregationWaitMs: number;
16
+ }
17
+ export interface JsonRpcRequest {
18
+ method: string;
19
+ params: Array<unknown>;
20
+ id: number;
21
+ jsonrpc: '2.0';
22
+ }
23
+ export interface JsonRpcResponse {
24
+ jsonrpc: '2.0';
25
+ id: number;
26
+ result?: unknown;
27
+ error?: {
28
+ code: number;
29
+ message: string;
30
+ data?: unknown;
31
+ };
32
+ }
33
+ export interface FullRequestIntent {
34
+ request: JsonRpcRequest;
35
+ resolve: (result: unknown) => void;
36
+ reject: (error: Error) => void;
37
+ }
38
+ export interface RequestIntent {
39
+ request: FullRequestIntent['request'];
40
+ resolve: FullRequestIntent['resolve'] | null;
41
+ reject: FullRequestIntent['reject'] | null;
42
+ }
43
+ export declare type PartialRequestIntent = RequestIntent | {
44
+ request: RequestIntent['request'];
45
+ resolve: null;
46
+ reject: null;
47
+ };
48
+ /**
49
+ * EIP-1898 support
50
+ * https://eips.ethereum.org/EIPS/eip-1898
51
+ */
52
+ declare module '@ethersproject/providers' {
53
+ interface JsonRpcProvider {
54
+ getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber>;
55
+ getTransactionCount(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<number>;
56
+ getCode(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
57
+ getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
58
+ call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
59
+ }
60
+ }
61
+ export declare class ExtendedJsonRpcBatchProvider extends JsonRpcProvider {
62
+ protected _batchAggregator: NodeJS.Timer | null;
63
+ protected _queue: Queue<FullRequestIntent>;
64
+ protected _requestPolicy: RequestPolicy;
65
+ protected _concurrencyLimiter: LimitFunction;
66
+ protected _tickCounter: number;
67
+ protected _fetchMiddlewareService: MiddlewareService<Promise<any>>;
68
+ constructor(url: ConnectionInfo | string, network?: Networkish, requestPolicy?: RequestPolicy, fetchMiddlewares?: MiddlewareCallback<Promise<any>>[]);
69
+ static _formatter: Formatter | null;
70
+ static getFormatter(): Formatter;
71
+ protected _batchAggregatorTick(): void;
72
+ protected _startBatchAggregator(): void;
73
+ use(callback: MiddlewareCallback<Promise<any>>): void;
74
+ send(method: string, params: Array<unknown>): Promise<unknown>;
75
+ detectNetwork(): Promise<Network>;
76
+ protected fetchJson(connection: string | ConnectionInfo, json?: string, processFunc?: (value: any, response: FetchJsonResponse) => any): Promise<any>;
77
+ }
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var properties = require('@ethersproject/properties');
7
+ var web = require('@ethersproject/web');
8
+ var providers = require('@ethersproject/providers');
9
+ var queue = require('../common/queue.js');
10
+ var fetch_error = require('../error/fetch.error.js');
11
+ var common = require('@nestjs/common');
12
+ var promiseLimit = require('../common/promise-limit.js');
13
+ var formatterWithEip1898 = require('../ethers/formatter-with-eip1898.js');
14
+ var middleware = require('@lido-nestjs/middleware');
15
+
16
+ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extends providers.JsonRpcProvider {
17
+ constructor(url, network, requestPolicy, fetchMiddlewares = []) {
18
+ super(url, network);
19
+ this._batchAggregator = null;
20
+ this._queue = new queue.Queue();
21
+ this._tickCounter = 0;
22
+ this._requestPolicy = requestPolicy !== null && requestPolicy !== void 0 ? requestPolicy : {
23
+ jsonRpcMaxBatchSize: 200,
24
+ maxConcurrentRequests: 5,
25
+ batchAggregationWaitMs: 10,
26
+ };
27
+ this._concurrencyLimiter = promiseLimit["default"](this._requestPolicy.maxConcurrentRequests);
28
+ this._fetchMiddlewareService = new middleware.MiddlewareService({
29
+ middlewares: fetchMiddlewares,
30
+ });
31
+ }
32
+ static getFormatter() {
33
+ if (this._formatter == null) {
34
+ this._formatter = new formatterWithEip1898.FormatterWithEIP1898();
35
+ }
36
+ return this._formatter;
37
+ }
38
+ _batchAggregatorTick() {
39
+ this._tickCounter++;
40
+ if (this._queue.length > this._requestPolicy.jsonRpcMaxBatchSize ||
41
+ this._tickCounter > 2) {
42
+ this._tickCounter = 0;
43
+ // getting multiple ('jsonRpcMaxBatchSize') elements from queue at once
44
+ // if queue size is less then 'jsonRpcMaxBatchSize' - dequeue remaining elements
45
+ const batch = this._queue.dequeueMultiple(this._requestPolicy.jsonRpcMaxBatchSize);
46
+ const batchRequest = batch.map((intent) => intent.request);
47
+ this.emit('debug', {
48
+ action: 'requestBatch',
49
+ request: properties.deepCopy(batchRequest),
50
+ provider: this,
51
+ });
52
+ this._concurrencyLimiter(() => {
53
+ return this._fetchMiddlewareService.go(() => this.fetchJson(this.connection, JSON.stringify(batchRequest)));
54
+ }).then((batchResult) => {
55
+ this.emit('debug', {
56
+ action: 'response',
57
+ request: properties.deepCopy(batchRequest),
58
+ response: properties.deepCopy(batchResult),
59
+ provider: this,
60
+ });
61
+ const resultMap = batchResult.reduce((resultMap, payload) => {
62
+ resultMap[payload.id] = payload;
63
+ return resultMap;
64
+ }, {});
65
+ // For each batch, feed it to the correct Promise, depending
66
+ // on whether it was a success or error
67
+ batch.forEach((inflightRequest) => {
68
+ const payload = resultMap[inflightRequest.request.id];
69
+ if (payload.error) {
70
+ const error = new fetch_error.FetchError(payload.error.message);
71
+ error.code = payload.error.code;
72
+ error.data = payload.error.data;
73
+ inflightRequest.reject(error);
74
+ }
75
+ else {
76
+ inflightRequest.resolve(payload.result);
77
+ }
78
+ });
79
+ }, (error) => {
80
+ this.emit('debug', {
81
+ action: 'response',
82
+ error: error,
83
+ request: properties.deepCopy(batchRequest),
84
+ provider: this,
85
+ });
86
+ batch.forEach((inflightRequest) => {
87
+ inflightRequest.reject(error);
88
+ });
89
+ });
90
+ }
91
+ this._batchAggregator && clearTimeout(this._batchAggregator);
92
+ this._batchAggregator = null;
93
+ // if the queue is not empty we should continue 'ticking'
94
+ // every 'batchAggregationWaitMs' time, until the queue is empty
95
+ if (this._queue.length > 0) {
96
+ this._startBatchAggregator();
97
+ }
98
+ }
99
+ _startBatchAggregator() {
100
+ if (!this._batchAggregator) {
101
+ // schedule batch for next event loop + short duration (macrotask)
102
+ this._batchAggregator = setTimeout(this._batchAggregatorTick.bind(this), this._requestPolicy.batchAggregationWaitMs);
103
+ }
104
+ }
105
+ use(callback) {
106
+ this._fetchMiddlewareService.use(callback);
107
+ }
108
+ send(method, params) {
109
+ const request = {
110
+ method: method,
111
+ params: params,
112
+ id: this._nextId++,
113
+ jsonrpc: '2.0',
114
+ };
115
+ const currentRequest = {
116
+ request,
117
+ reject: null,
118
+ resolve: null,
119
+ };
120
+ const promise = new Promise((resolve, reject) => {
121
+ currentRequest.resolve = resolve;
122
+ currentRequest.reject = reject;
123
+ });
124
+ this._queue.enqueue(currentRequest);
125
+ this._startBatchAggregator();
126
+ return promise;
127
+ }
128
+ async detectNetwork() {
129
+ let network = this.network;
130
+ if (network == null) {
131
+ network = await super.detectNetwork();
132
+ // If still not set, set it
133
+ if (this._network == null) {
134
+ // A static network does not support "any"
135
+ properties.defineReadOnly(this, '_network', network);
136
+ this.emit('network', network, null);
137
+ }
138
+ }
139
+ return network;
140
+ }
141
+ async fetchJson(connection, json, processFunc) {
142
+ return await web.fetchJson(connection, json, processFunc);
143
+ }
144
+ };
145
+ exports.ExtendedJsonRpcBatchProvider._formatter = null;
146
+ exports.ExtendedJsonRpcBatchProvider = tslib.__decorate([
147
+ common.Injectable(),
148
+ tslib.__metadata("design:paramtypes", [Object, Object, Object, Array])
149
+ ], exports.ExtendedJsonRpcBatchProvider);
@@ -0,0 +1,39 @@
1
+ import { BaseProvider, Formatter } from '@ethersproject/providers';
2
+ import { SimpleFallbackProviderConfig } from '../interfaces/simple-fallback-provider-config';
3
+ import { Network } from '@ethersproject/networks';
4
+ import { LoggerService } from '@nestjs/common';
5
+ import { FallbackProvider } from '../interfaces/fallback-provider';
6
+ import { BlockTag } from '../ethers/block-tag';
7
+ import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
8
+ import { Deferrable } from '@ethersproject/properties';
9
+ import { TransactionRequest } from '@ethersproject/abstract-provider/src.ts/index';
10
+ /**
11
+ * EIP-1898 support
12
+ * https://eips.ethereum.org/EIPS/eip-1898
13
+ */
14
+ declare module '@ethersproject/providers' {
15
+ interface BaseProvider {
16
+ getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber>;
17
+ getTransactionCount(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<number>;
18
+ getCode(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
19
+ getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
20
+ call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
21
+ }
22
+ }
23
+ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
24
+ protected config: SimpleFallbackProviderConfig;
25
+ protected logger: LoggerService;
26
+ protected fallbackProviders: [FallbackProvider];
27
+ protected activeFallbackProviderIndex: number;
28
+ protected detectNetworkFirstRun: boolean;
29
+ constructor(config: SimpleFallbackProviderConfig, logger: LoggerService);
30
+ static _formatter: Formatter | null;
31
+ static getFormatter(): Formatter;
32
+ protected get provider(): FallbackProvider;
33
+ protected switchToNextProvider(): void;
34
+ perform(method: string, params: {
35
+ [name: string]: unknown;
36
+ }): Promise<unknown>;
37
+ detectNetwork(): Promise<Network>;
38
+ protected networksEqual(networkA: Network, networkB: Network): boolean;
39
+ }
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var providers = require('@ethersproject/providers');
7
+ var extendedJsonRpcBatchProvider = require('./extended-json-rpc-batch-provider.js');
8
+ var common = require('@nestjs/common');
9
+ var retrier = require('../common/retrier.js');
10
+ var formatterWithEip1898 = require('../ethers/formatter-with-eip1898.js');
11
+ var networks = require('../common/networks.js');
12
+
13
+ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchProvider extends providers.BaseProvider {
14
+ constructor(config, logger) {
15
+ super(config.network);
16
+ this.detectNetworkFirstRun = true;
17
+ this.config = Object.assign({ maxRetries: 3, minBackoffMs: 500, maxBackoffMs: 5000, logRetries: true }, config);
18
+ this.logger = logger;
19
+ const conns = config.urls.filter((url) => {
20
+ if (!url) {
21
+ return false;
22
+ }
23
+ if (typeof url === 'object' && !url.url) {
24
+ return false;
25
+ }
26
+ return true;
27
+ });
28
+ if (conns.length < 1) {
29
+ throw new Error('No valid URLs or Connections were provided');
30
+ }
31
+ this.fallbackProviders = conns.map((conn, index) => {
32
+ var _a;
33
+ const provider = new extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider(conn, undefined, config.requestPolicy, (_a = config.fetchMiddlewares) !== null && _a !== void 0 ? _a : []);
34
+ return {
35
+ network: null,
36
+ provider,
37
+ index,
38
+ };
39
+ });
40
+ this.activeFallbackProviderIndex = 0;
41
+ }
42
+ static getFormatter() {
43
+ if (this._formatter == null) {
44
+ this._formatter = new formatterWithEip1898.FormatterWithEIP1898();
45
+ }
46
+ return this._formatter;
47
+ }
48
+ get provider() {
49
+ if (this.activeFallbackProviderIndex > this.fallbackProviders.length - 1) {
50
+ this.activeFallbackProviderIndex = 0;
51
+ }
52
+ let fallbackProvider = this.fallbackProviders[this.activeFallbackProviderIndex];
53
+ let attempt = 0;
54
+ const isValid = (provider) => provider.network !== null &&
55
+ provider.network.chainId === networks.getNetworkChain(this.config.network);
56
+ while (!isValid(fallbackProvider) ||
57
+ attempt < this.fallbackProviders.length) {
58
+ fallbackProvider =
59
+ this.fallbackProviders[this.activeFallbackProviderIndex];
60
+ // skipping providers with unreachable endpoints or networks
61
+ // that are not equal to predefined network (from config)
62
+ if (!isValid(fallbackProvider)) {
63
+ this.activeFallbackProviderIndex++;
64
+ }
65
+ attempt++;
66
+ }
67
+ return fallbackProvider;
68
+ }
69
+ switchToNextProvider() {
70
+ if (this.fallbackProviders.length === 1) {
71
+ this.logger.warn('Will not switch to next provider. No valid backup provider provided.');
72
+ return;
73
+ }
74
+ this.activeFallbackProviderIndex++;
75
+ this.logger.log(`Switched to next provider for execution layer`);
76
+ }
77
+ async perform(method, params) {
78
+ const retry = retrier.retrier(this.logger, this.config.maxRetries, this.config.minBackoffMs, this.config.maxBackoffMs, this.config.logRetries);
79
+ let attempt = 0;
80
+ // will perform maximum `this.config.maxRetries` retries for fetching data with single provider
81
+ // after failure will switch to next provider
82
+ // maximum number of switching is limited to total fallback provider count
83
+ while (attempt < this.fallbackProviders.length) {
84
+ try {
85
+ attempt++;
86
+ // awaiting is extremely important here
87
+ // without it, the error will not be caught in current try-catch scope
88
+ return await retry(() => this.provider.provider.perform(method, params));
89
+ }
90
+ catch (e) {
91
+ this.logger.error('Error while doing ETH1 RPC request. Will try to switch to another provider');
92
+ this.logger.error(e);
93
+ this.switchToNextProvider();
94
+ }
95
+ }
96
+ throw new Error('All attempts to do ETH1 RPC request failed');
97
+ }
98
+ async detectNetwork() {
99
+ const results = await Promise.allSettled(this.fallbackProviders.map((c) => c.provider.getNetwork()));
100
+ results.forEach((result, i) => {
101
+ if (result.status === 'fulfilled') {
102
+ this.fallbackProviders[i].network = result.value;
103
+ }
104
+ else {
105
+ this.fallbackProviders[i].network = null;
106
+ }
107
+ });
108
+ let previousNetwork = null;
109
+ this.fallbackProviders.forEach((fallbackProvider, index) => {
110
+ if (!fallbackProvider.network) {
111
+ return;
112
+ }
113
+ if (!networks.networksChainsEqual(fallbackProvider.network, this.config.network)) {
114
+ if (this.detectNetworkFirstRun) {
115
+ throw new Error(`Fallback provider [${index}] network chainId ` +
116
+ `[${fallbackProvider.network.chainId}] is different to network ` +
117
+ `chainId from config [${networks.getNetworkChain(this.config.network)}]`);
118
+ }
119
+ // TODO add logs here
120
+ // skipping network with bad chainId
121
+ return;
122
+ }
123
+ if (previousNetwork) {
124
+ // Make sure the fallbackProvider network matches the previous network
125
+ if (!this.networksEqual(previousNetwork, fallbackProvider.network)) {
126
+ if (this.detectNetworkFirstRun) {
127
+ throw new Error(`Fallback provider [${index}] network is different to other provider's networks`);
128
+ }
129
+ this.logger.warn(`Fallback provider [${index}] network is different to other provider's networks`);
130
+ }
131
+ }
132
+ else {
133
+ previousNetwork = fallbackProvider.network;
134
+ }
135
+ });
136
+ if (!previousNetwork) {
137
+ throw new Error('All fallback endpoints are unreachable or all fallback networks differ between each other');
138
+ }
139
+ if (this.detectNetworkFirstRun) {
140
+ this.detectNetworkFirstRun = false;
141
+ }
142
+ return previousNetwork;
143
+ }
144
+ networksEqual(networkA, networkB) {
145
+ return networks.networksEqual(networkA, networkB);
146
+ }
147
+ };
148
+ exports.SimpleFallbackJsonRpcBatchProvider._formatter = null;
149
+ exports.SimpleFallbackJsonRpcBatchProvider = tslib.__decorate([
150
+ common.Injectable(),
151
+ tslib.__metadata("design:paramtypes", [Object, Object])
152
+ ], exports.SimpleFallbackJsonRpcBatchProvider);
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@lido-nestjs/execution",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/lidofinance/lido-nestjs-modules",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/lidofinance/lido-nestjs-modules.git",
11
+ "directory": "packages/execution"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/lidofinance/lido-nestjs-modules/issues"
15
+ },
16
+ "sideEffects": false,
17
+ "keywords": [
18
+ "lido",
19
+ "lido-nestjs",
20
+ "lido-nestjs-modules",
21
+ "lidofinance",
22
+ "execution",
23
+ "execution-layer",
24
+ "provider"
25
+ ],
26
+ "files": [
27
+ "dist/*"
28
+ ],
29
+ "scripts": {
30
+ "lint": "eslint --ext ts ."
31
+ },
32
+ "publishConfig": {
33
+ "registry": "https://registry.npmjs.org/",
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@ethersproject/networks": "^5.5.2",
38
+ "@ethersproject/properties": "^5.5.0",
39
+ "@ethersproject/providers": "^5.5.3",
40
+ "@ethersproject/web": "^5.5.1",
41
+ "@lido-nestjs/logger": "1.0.3",
42
+ "@lido-nestjs/middleware": "1.1.1"
43
+ },
44
+ "peerDependencies": {
45
+ "@nestjs/common": "^8.2.5",
46
+ "@nestjs/core": "^8.2.5",
47
+ "reflect-metadata": "^0.1.13",
48
+ "rxjs": "^7.5.2",
49
+ "tslib": "^2.3.1"
50
+ },
51
+ "devDependencies": {
52
+ "@nestjs/common": "^8.2.5",
53
+ "@nestjs/core": "^8.2.5",
54
+ "@nestjs/testing": "^8.2.5",
55
+ "reflect-metadata": "^0.1.13",
56
+ "rxjs": "^7.5.2"
57
+ }
58
+ }