From f392ae9ac3197bf94e2c120798d60e24d2af27f2 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 11 Sep 2023 12:37:25 +0200 Subject: [PATCH 01/10] Add support for eth1 provider state --- .../src/eth1/eth1DepositDataTracker.ts | 4 +- packages/beacon-node/src/eth1/interface.ts | 8 ++ .../src/eth1/provider/eth1Provider.ts | 47 ++++++- .../src/eth1/provider/jsonRpcHttpClient.ts | 125 ++++++++++++------ .../beacon-node/src/execution/engine/http.ts | 30 ++--- .../beacon-node/src/execution/engine/utils.ts | 37 ++++-- 6 files changed, 178 insertions(+), 73 deletions(-) diff --git a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts index a7fc91fafb06..92b922cbf9f5 100644 --- a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts +++ b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts @@ -164,6 +164,7 @@ export class Eth1DepositDataTracker { while (!this.signal.aborted) { lastRunMs = Date.now(); + const oldState = this.eth1Provider.getState(); try { const hasCaughtUp = await this.update(); @@ -179,7 +180,8 @@ export class Eth1DepositDataTracker { if (e instanceof HttpRpcError && e.status === 429) { this.logger.debug("Eth1 provider rate limited", {}, e); await sleep(RATE_LIMITED_WAIT_MS, this.signal); - } else if (!isErrorAborted(e)) { + // only log error if state switched from online to some other state + } else if (!isErrorAborted(e) && this.eth1Provider.getState() !== oldState) { this.logger.error("Error updating eth1 chain cache", {}, e as Error); await sleep(MIN_WAIT_ON_ERROR_MS, this.signal); } diff --git a/packages/beacon-node/src/eth1/interface.ts b/packages/beacon-node/src/eth1/interface.ts index 550213f9b252..fc9626eb5b8a 100644 --- a/packages/beacon-node/src/eth1/interface.ts +++ b/packages/beacon-node/src/eth1/interface.ts @@ -29,6 +29,14 @@ export interface IEth1Provider { getBlocksByNumber(fromBlock: number, toBlock: number): Promise; getDepositEvents(fromBlock: number, toBlock: number): Promise; validateContract(): Promise; + getState(): Eth1ProviderState; +} + +export enum Eth1ProviderState { + ONLINE = "ONLINE", + OFFLINE = "OFFLINE", + ERROR = "ERROR", + AUTH_FAILED = "AUTH_FAILED", } export type Eth1DataAndDeposits = { diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index 3fe5913d7a87..ebc1e24427ce 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -1,15 +1,24 @@ import {toHexString} from "@chainsafe/ssz"; import {phase0} from "@lodestar/types"; import {ChainConfig} from "@lodestar/config"; -import {fromHex} from "@lodestar/utils"; +import {fromHex, isErrorAborted} from "@lodestar/utils"; +import {FetchError, isFetchError} from "@lodestar/api"; import {linspace} from "../../util/numpy.js"; import {depositEventTopics, parseDepositLog} from "../utils/depositContract.js"; -import {Eth1Block, IEth1Provider} from "../interface.js"; +import {Eth1Block, Eth1ProviderState, IEth1Provider} from "../interface.js"; import {DEFAULT_PROVIDER_URLS, Eth1Options} from "../options.js"; import {isValidAddress} from "../../util/address.js"; import {EthJsonRpcBlockRaw} from "../interface.js"; -import {JsonRpcHttpClient, JsonRpcHttpClientMetrics, ReqOpts} from "./jsonRpcHttpClient.js"; +import {HTTP_CONNECTION_ERROR_CODES, HTTP_FATAL_ERROR_CODES} from "../../execution/engine/utils.js"; +import { + ErrorJsonRpcResponse, + HttpRpcError, + JsonRpcHttpClient, + JsonRpcHttpClientEvent, + JsonRpcHttpClientMetrics, + ReqOpts, +} from "./jsonRpcHttpClient.js"; import {isJsonRpcTruncatedError, quantityToNum, numToQuantity, dataToBytes} from "./utils.js"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -46,6 +55,8 @@ export class Eth1Provider implements IEth1Provider { readonly deployBlock: number; private readonly depositContractAddress: string; private readonly rpc: JsonRpcHttpClient; + // The default state is ONLINE, it will be updated to offline if we receive a http error + private state: Eth1ProviderState = Eth1ProviderState.ONLINE; constructor( config: Pick, @@ -62,6 +73,36 @@ export class Eth1Provider implements IEth1Provider { jwtSecret: opts.jwtSecretHex ? fromHex(opts.jwtSecretHex) : undefined, metrics: metrics, }); + + this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => { + this.state = Eth1ProviderState.ONLINE; + }); + + this.rpc.emitter.on(JsonRpcHttpClientEvent.ERROR, ({error}) => { + if (isErrorAborted(error)) { + this.state = Eth1ProviderState.ONLINE; + return; + } + + if ((error as unknown) instanceof HttpRpcError || (error as unknown) instanceof ErrorJsonRpcResponse) { + this.state = Eth1ProviderState.ERROR; + return; + } + + if (error && isFetchError(error) && HTTP_FATAL_ERROR_CODES.includes((error as FetchError).code)) { + this.state = Eth1ProviderState.OFFLINE; + return; + } + + if (error && isFetchError(error) && HTTP_CONNECTION_ERROR_CODES.includes((error as FetchError).code)) { + this.state = Eth1ProviderState.AUTH_FAILED; + return; + } + }); + } + + getState(): Eth1ProviderState { + return this.state; } async validateContract(): Promise { diff --git a/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts b/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts index 59454f8c2f9e..b5068e94f2ef 100644 --- a/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts +++ b/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts @@ -1,8 +1,33 @@ +import {EventEmitter} from "events"; +import StrictEventEmitter from "strict-event-emitter-types"; import {fetch} from "@lodestar/api"; import {ErrorAborted, TimeoutError, isValidHttpUrl, retry} from "@lodestar/utils"; import {IGauge, IHistogram} from "../../metrics/interface.js"; import {IJson, RpcPayload} from "../interface.js"; import {encodeJwtToken} from "./jwt.js"; + +export const enum JsonRpcHttpClientEvent { + /** + * When registered this event will be emitted before the client throws an error. + * This is useful for defining the error behavior in a common place at the time of declaration of the client. + */ + ERROR = "jsonRpcHttpClient:error", + /** + * When registered this event will be emitted before the client returns the valid response to the caller. + * This is useful for defining some common behavior for each request/response cycle + */ + RESPONSE = "jsonRpcHttpClient:response", +} + +export type JsonRpcHttpClientEvents = { + [JsonRpcHttpClientEvent.ERROR]: (event: {payload?: unknown; error: Error}) => void; + [JsonRpcHttpClientEvent.RESPONSE]: (event: {payload?: unknown; response: unknown}) => void; +}; + +export class JsonRpcHttpClientEventEmitter extends (EventEmitter as { + new (): StrictEventEmitter; +}) {} + /** * Limits the amount of response text printed with RPC or parsing errors */ @@ -46,6 +71,7 @@ export interface IJsonRpcHttpClient { fetch(payload: RpcPayload

, opts?: ReqOpts): Promise; fetchWithRetries(payload: RpcPayload

, opts?: ReqOpts): Promise; fetchBatch(rpcPayloadArr: RpcPayload[], opts?: ReqOpts): Promise; + emitter: JsonRpcHttpClientEventEmitter; } export class JsonRpcHttpClient implements IJsonRpcHttpClient { @@ -58,6 +84,7 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { */ private readonly jwtSecret?: Uint8Array; private readonly metrics: JsonRpcHttpClientMetrics | null; + readonly emitter = new JsonRpcHttpClientEventEmitter(); constructor( private readonly urls: string[], @@ -107,31 +134,38 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { * Perform RPC request */ async fetch(payload: RpcPayload

, opts?: ReqOpts): Promise { - const res: RpcResponse = await this.fetchJson({jsonrpc: "2.0", id: this.id++, ...payload}, opts); - return parseRpcResponse(res, payload); + return this.wrapWithEvents( + async () => { + const res: RpcResponse = await this.fetchJson({jsonrpc: "2.0", id: this.id++, ...payload}, opts); + return parseRpcResponse(res, payload); + }, + {payload} + ); } /** * Perform RPC request with retry */ async fetchWithRetries(payload: RpcPayload

, opts?: ReqOpts): Promise { - const routeId = opts?.routeId ?? "unknown"; - - const res = await retry>( - async (attempt) => { - /** If this is a retry, increment the retry counter for this method */ - if (attempt > 1) { - this.opts?.metrics?.retryCount.inc({routeId}); + return this.wrapWithEvents(async () => { + const routeId = opts?.routeId ?? "unknown"; + + const res = await retry>( + async (attempt) => { + /** If this is a retry, increment the retry counter for this method */ + if (attempt > 1) { + this.opts?.metrics?.retryCount.inc({routeId}); + } + return this.fetchJson({jsonrpc: "2.0", id: this.id++, ...payload}, opts); + }, + { + retries: opts?.retryAttempts ?? this.opts?.retryAttempts ?? 1, + retryDelay: opts?.retryDelay ?? this.opts?.retryDelay ?? 0, + shouldRetry: opts?.shouldRetry, } - return this.fetchJson({jsonrpc: "2.0", id: this.id++, ...payload}, opts); - }, - { - retries: opts?.retryAttempts ?? this.opts?.retryAttempts ?? 1, - retryDelay: opts?.retryDelay ?? this.opts?.retryDelay ?? 0, - shouldRetry: opts?.shouldRetry, - } - ); - return parseRpcResponse(res, payload); + ); + return parseRpcResponse(res, payload); + }, payload); } /** @@ -139,26 +173,41 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { * Type-wise assumes all requests results have the same type */ async fetchBatch(rpcPayloadArr: RpcPayload[], opts?: ReqOpts): Promise { - if (rpcPayloadArr.length === 0) return []; - - const resArr: RpcResponse[] = await this.fetchJson( - rpcPayloadArr.map(({method, params}) => ({jsonrpc: "2.0", method, params, id: this.id++})), - opts - ); + return this.wrapWithEvents(async () => { + if (rpcPayloadArr.length === 0) return []; + + const resArr: RpcResponse[] = await this.fetchJson( + rpcPayloadArr.map(({method, params}) => ({jsonrpc: "2.0", method, params, id: this.id++})), + opts + ); + + if (!Array.isArray(resArr)) { + // Nethermind may reply to batch request with a JSON RPC error + if ((resArr as RpcResponseError).error !== undefined) { + throw new ErrorJsonRpcResponse(resArr as RpcResponseError, "batch"); + } - if (!Array.isArray(resArr)) { - // Nethermind may reply to batch request with a JSON RPC error - if ((resArr as RpcResponseError).error !== undefined) { - throw new ErrorJsonRpcResponse(resArr as RpcResponseError, "batch"); + throw Error(`expected array of results, got ${resArr} - ${jsonSerializeTry(resArr)}`); } - throw Error(`expected array of results, got ${resArr} - ${jsonSerializeTry(resArr)}`); - } + return resArr.map((res, i) => parseRpcResponse(res, rpcPayloadArr[i])); + }, rpcPayloadArr); + } - return resArr.map((res, i) => parseRpcResponse(res, rpcPayloadArr[i])); + private async wrapWithEvents(func: () => Promise, payload?: unknown): Promise { + try { + const response = await func(); + this.emitter.emit(JsonRpcHttpClientEvent.RESPONSE, {payload, response}); + return response; + } catch (error) { + this.emitter.emit(JsonRpcHttpClientEvent.ERROR, {payload, error: error as Error}); + throw error; + } } private async fetchJson(json: T, opts?: ReqOpts): Promise { + if (this.urls.length === 0) throw Error("No url provided"); + const routeId = opts?.routeId ?? "unknown"; let lastError: Error | null = null; @@ -170,21 +219,13 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { try { return await this.fetchJsonOneUrl(this.urls[i], json, opts); } catch (e) { + lastError = e as Error; if (this.opts?.shouldNotFallback?.(e as Error)) { - throw e; + break; } - - lastError = e as Error; } } - - if (lastError !== null) { - throw lastError; - } else if (this.urls.length === 0) { - throw Error("No url provided"); - } else { - throw Error("Unknown error"); - } + throw lastError ?? Error("Unknown error"); } /** diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index 9be9b0d3be99..6f5b3553dcb4 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -5,13 +5,13 @@ import { ErrorJsonRpcResponse, HttpRpcError, IJsonRpcHttpClient, + JsonRpcHttpClientEvent, ReqOpts, } from "../../eth1/provider/jsonRpcHttpClient.js"; import {Metrics} from "../../metrics/index.js"; import {JobItemQueue} from "../../util/queue/index.js"; import {EPOCHS_PER_BATCH} from "../../sync/constants.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; -import {IJson, RpcPayload} from "../../eth1/interface.js"; import { ExecutionPayloadStatus, ExecutePayloadResponse, @@ -110,7 +110,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { private readonly rpcFetchQueue: JobItemQueue<[EngineRequest], EngineResponse>; private jobQueueProcessor = async ({method, params, methodOpts}: EngineRequest): Promise => { - return this.fetchWithRetries( + return this.rpc.fetchWithRetries( {method, params}, methodOpts ); @@ -126,22 +126,14 @@ export class ExecutionEngineHttp implements IExecutionEngine { metrics?.engineHttpProcessorQueue ); this.logger = logger; - } - protected async fetchWithRetries(payload: RpcPayload

, opts?: ReqOpts): Promise { - try { - const res = await this.rpc.fetchWithRetries(payload, opts); + this.rpc.emitter.on(JsonRpcHttpClientEvent.ERROR, ({error}) => { + this.updateEngineState(getExecutionEngineState({payloadError: error, oldState: this.state})); + }); + + this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => { this.updateEngineState(getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state})); - return res; - } catch (err) { - this.updateEngineState(getExecutionEngineState({payloadError: err, oldState: this.state})); - - /* - * TODO: For some error cases as abort, we may not want to escalate the error to the caller - * But for now the higher level code handles such cases so we can just rethrow the error - */ - throw err; - } + }); } /** @@ -370,7 +362,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { : ForkSeq[fork] >= ForkSeq.capella ? "engine_getPayloadV2" : "engine_getPayloadV1"; - const payloadResponse = await this.fetchWithRetries< + const payloadResponse = await this.rpc.fetchWithRetries< EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method] >( @@ -390,7 +382,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { async getPayloadBodiesByHash(blockHashes: RootHex[]): Promise<(ExecutionPayloadBody | null)[]> { const method = "engine_getPayloadBodiesByHashV1"; assertReqSizeLimit(blockHashes.length, 32); - const response = await this.fetchWithRetries< + const response = await this.rpc.fetchWithRetries< EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method] >({method, params: [blockHashes]}); @@ -405,7 +397,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { assertReqSizeLimit(blockCount, 32); const start = numToQuantity(startBlockNumber); const count = numToQuantity(blockCount); - const response = await this.fetchWithRetries< + const response = await this.rpc.fetchWithRetries< EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method] >({method, params: [start, count]}); diff --git a/packages/beacon-node/src/execution/engine/utils.ts b/packages/beacon-node/src/execution/engine/utils.ts index 13ad8d855062..e661af8daf70 100644 --- a/packages/beacon-node/src/execution/engine/utils.ts +++ b/packages/beacon-node/src/execution/engine/utils.ts @@ -1,7 +1,13 @@ import {isFetchError} from "@lodestar/api"; import {isErrorAborted} from "@lodestar/utils"; import {IJson, RpcPayload} from "../../eth1/interface.js"; -import {IJsonRpcHttpClient, ErrorJsonRpcResponse, HttpRpcError} from "../../eth1/provider/jsonRpcHttpClient.js"; +import { + IJsonRpcHttpClient, + ErrorJsonRpcResponse, + HttpRpcError, + JsonRpcHttpClientEventEmitter, + JsonRpcHttpClientEvent, +} from "../../eth1/provider/jsonRpcHttpClient.js"; import {isQueueErrorAborted} from "../../util/queue/errors.js"; import {ExecutionPayloadStatus, ExecutionEngineState} from "./interface.js"; @@ -11,16 +17,20 @@ export type JsonRpcBackend = { }; export class ExecutionEngineMockJsonRpcClient implements IJsonRpcHttpClient { + readonly emitter = new JsonRpcHttpClientEventEmitter(); + constructor(private readonly backend: JsonRpcBackend) {} async fetch(payload: RpcPayload

): Promise { - const handler = this.backend.handlers[payload.method]; - if (handler === undefined) { - throw Error(`Unknown method ${payload.method}`); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return handler(...(payload.params as any[])) as R; + return this.wrapWithEvents(async () => { + const handler = this.backend.handlers[payload.method]; + if (handler === undefined) { + throw Error(`Unknown method ${payload.method}`); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return handler(...(payload.params as any[])) as R; + }, payload); } fetchWithRetries(payload: RpcPayload

): Promise { @@ -30,6 +40,17 @@ export class ExecutionEngineMockJsonRpcClient implements IJsonRpcHttpClient { fetchBatch(rpcPayloadArr: RpcPayload[]): Promise { return Promise.all(rpcPayloadArr.map((payload) => this.fetch(payload))); } + + private async wrapWithEvents(func: () => Promise, payload?: unknown): Promise { + try { + const response = await func(); + this.emitter.emit(JsonRpcHttpClientEvent.RESPONSE, {payload, response}); + return response; + } catch (error) { + this.emitter.emit(JsonRpcHttpClientEvent.ERROR, {payload, error: error as Error}); + throw error; + } + } } export const HTTP_FATAL_ERROR_CODES = ["ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN"]; From cbcce567feb30cf8127ed79be52a0a56bc8dce58 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 11 Sep 2023 15:39:52 +0200 Subject: [PATCH 02/10] Fix the tests types --- packages/beacon-node/test/unit/chain/bls/bls.test.ts | 2 +- packages/beacon-node/test/unit/chain/genesis/genesis.test.ts | 3 ++- .../beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/test/unit/chain/bls/bls.test.ts b/packages/beacon-node/test/unit/chain/bls/bls.test.ts index 89a5e48a9db9..f2da1d0a886b 100644 --- a/packages/beacon-node/test/unit/chain/bls/bls.test.ts +++ b/packages/beacon-node/test/unit/chain/bls/bls.test.ts @@ -4,7 +4,7 @@ import {CoordType} from "@chainsafe/blst"; import {PublicKey} from "@chainsafe/bls/types"; import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; import {BlsSingleThreadVerifier} from "../../../../src/chain/bls/singleThread.js"; -import {BlsMultiThreadWorkerPool} from "../../../../lib/chain/bls/multithread/index.js"; +import {BlsMultiThreadWorkerPool} from "../../../../src/chain/bls/multithread/index.js"; import {testLogger} from "../../../utils/logger.js"; describe("BlsVerifier ", function () { diff --git a/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts b/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts index 78be9a88e4be..eb117646f7fc 100644 --- a/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts +++ b/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts @@ -10,7 +10,7 @@ import {ErrorAborted} from "@lodestar/utils"; import {GenesisBuilder} from "../../../../src/chain/genesis/genesis.js"; import {testLogger} from "../../../utils/logger.js"; import {ZERO_HASH_HEX} from "../../../../src/constants/index.js"; -import {EthJsonRpcBlockRaw, IEth1Provider} from "../../../../src/eth1/interface.js"; +import {Eth1ProviderState, EthJsonRpcBlockRaw, IEth1Provider} from "../../../../src/eth1/interface.js"; describe("genesis builder", function () { const logger = testLogger(); @@ -62,6 +62,7 @@ describe("genesis builder", function () { validateContract: async () => { return; }, + getState: () => Eth1ProviderState.ONLINE, ...eth1Provider, }; } diff --git a/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts b/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts index f9dafcf1fe7e..828573bbb06b 100644 --- a/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts +++ b/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts @@ -5,7 +5,7 @@ import {sleep} from "@lodestar/utils"; import {IEth1Provider} from "../../../src/index.js"; import {ZERO_HASH} from "../../../src/constants/index.js"; import {Eth1MergeBlockTracker, StatusCode, toPowBlock} from "../../../src/eth1/eth1MergeBlockTracker.js"; -import {EthJsonRpcBlockRaw} from "../../../src/eth1/interface.js"; +import {Eth1ProviderState, EthJsonRpcBlockRaw} from "../../../src/eth1/interface.js"; import {testLogger} from "../../utils/logger.js"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -57,6 +57,7 @@ describe("eth1 / Eth1MergeBlockTracker", () => { validateContract: async (): Promise => { throw Error("Not implemented"); }, + getState: () => Eth1ProviderState.ONLINE, }; const eth1MergeBlockTracker = new Eth1MergeBlockTracker( @@ -133,6 +134,7 @@ describe("eth1 / Eth1MergeBlockTracker", () => { validateContract: async (): Promise => { throw Error("Not implemented"); }, + getState: () => Eth1ProviderState.ONLINE, }; await runFindMergeBlockTest(eth1Provider, blocks[blocks.length - 1]); @@ -216,6 +218,7 @@ describe("eth1 / Eth1MergeBlockTracker", () => { validateContract: async (): Promise => { throw Error("Not implemented"); }, + getState: () => Eth1ProviderState.ONLINE, }; } From a95f52cb4eead65f82af4990ecc0a4a792a404b7 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 28 Sep 2023 11:33:55 +0200 Subject: [PATCH 03/10] Fix transient enum const type error --- packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts b/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts index b5068e94f2ef..9207dc21909f 100644 --- a/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts +++ b/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts @@ -6,7 +6,7 @@ import {IGauge, IHistogram} from "../../metrics/interface.js"; import {IJson, RpcPayload} from "../interface.js"; import {encodeJwtToken} from "./jwt.js"; -export const enum JsonRpcHttpClientEvent { +export enum JsonRpcHttpClientEvent { /** * When registered this event will be emitted before the client throws an error. * This is useful for defining the error behavior in a common place at the time of declaration of the client. From ee92aa7c7ff38f409f9f673be11fbd51bda694d5 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 28 Sep 2023 16:07:40 +0200 Subject: [PATCH 04/10] Add a utility for elapsed time --- packages/utils/src/waitFor.ts | 42 +++++++++++++++++ packages/utils/test/unit/waitFor.test.ts | 57 +++++++++++++++++++++++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/waitFor.ts b/packages/utils/src/waitFor.ts index 91206267e6ca..04d0b0fa04b6 100644 --- a/packages/utils/src/waitFor.ts +++ b/packages/utils/src/waitFor.ts @@ -51,3 +51,45 @@ export function waitFor(condition: () => boolean, opts: WaitForOpts = {}): Promi }; }); } + +interface ElapsedTimeTrackerAttributes { + msSinceLastError?: number; + msSinceLastCall?: number; + now: number; +} + +interface ElapsedTimeTrackerOptions { + minElapsedTime: number; + onError?: (data: ElapsedTimeTrackerAttributes) => void; +} + +type ElapsedTimeTrackerCaller = (data: ElapsedTimeTrackerAttributes) => void; +export type ElapsedTimeTracker = (fn: ElapsedTimeTrackerCaller) => void; + +/** + * Create a tracker which keeps track of the last time a function was called + * + * @param durationMs + * @returns + */ +export function waitForElapsedTime({minElapsedTime, onError}: ElapsedTimeTrackerOptions): ElapsedTimeTracker { + // Initialized with undefined as the function has not been called yet + let lastTimeCalled: number | undefined = undefined; + let lastTimeError: number | undefined = undefined; + + return function elapsedTimeTracker(fn: ElapsedTimeTrackerCaller): void { + const now = Date.now(); + + const msSinceLastCall = lastTimeCalled === undefined ? undefined : now - lastTimeCalled; + const msSinceLastError = lastTimeError === undefined ? undefined : now - lastTimeError; + + if (msSinceLastCall !== undefined && msSinceLastCall < minElapsedTime) { + if (onError) onError({now, msSinceLastError, msSinceLastCall}); + lastTimeError = now; + return; + } + + fn({now, msSinceLastCall, msSinceLastError}); + lastTimeCalled = now; + }; +} diff --git a/packages/utils/test/unit/waitFor.test.ts b/packages/utils/test/unit/waitFor.test.ts index 1dd3dec766b7..da5a1fec1bba 100644 --- a/packages/utils/test/unit/waitFor.test.ts +++ b/packages/utils/test/unit/waitFor.test.ts @@ -1,7 +1,9 @@ import "../setup.js"; import {expect} from "chai"; -import {waitFor} from "../../src/waitFor.js"; +import sinon from "sinon"; +import {waitFor, waitForElapsedTime} from "../../src/waitFor.js"; import {ErrorAborted, TimeoutError} from "../../src/errors.js"; +import {sleep} from "../../src/sleep.js"; describe("waitFor", () => { const interval = 10; @@ -35,3 +37,56 @@ describe("waitFor", () => { await expect(waitFor(() => true, {interval, timeout, signal: controller.signal})).to.be.rejectedWith(ErrorAborted); }); }); + +describe("waitForElapsedTime", () => { + it("should call the function for the first time", () => { + const callIfTimePassed = waitForElapsedTime({minElapsedTime: 1000}); + const fn = sinon.spy(); + callIfTimePassed(fn); + + expect(fn).to.have.been.calledOnce; + }); + + it("should call the function after the minElapsedTime has passed", async () => { + const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100}); + const fn = sinon.spy(); + callIfTimePassed(fn); + + await sleep(150); + callIfTimePassed(fn); + + expect(fn).to.have.been.calledTwice; + }); + + it("should not call the function before the minElapsedTime has passed", () => { + const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100}); + const fn = sinon.spy(); + callIfTimePassed(fn); + callIfTimePassed(fn); + + expect(fn).to.have.been.calledOnce; + }); + + it("should call the onError if the function is called before the minElapsedTime has passed", () => { + const fn = sinon.spy(); + const err = sinon.spy(); + const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100, onError: err}); + callIfTimePassed(fn); + callIfTimePassed(fn); + + expect(err).to.have.been.calledOnce; + }); + + it("should not call the onError if the function is called after the minElapsedTime has passed", async () => { + const fn = sinon.spy(); + const err = sinon.spy(); + const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100, onError: err}); + callIfTimePassed(fn); + + await sleep(100); + + callIfTimePassed(fn); + + expect(err).to.have.been.callCount(0); + }); +}); From ed24aacd9f5183e19b6cbef5a8ed725f0f80afe3 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 28 Sep 2023 16:08:44 +0200 Subject: [PATCH 05/10] Update the eth1 provider to check elapsed time --- packages/beacon-node/src/chain/initState.ts | 2 +- .../src/eth1/eth1DepositDataTracker.ts | 5 +-- packages/beacon-node/src/eth1/index.ts | 8 +++- .../src/eth1/provider/eth1Provider.ts | 41 ++++++++++++------- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/packages/beacon-node/src/chain/initState.ts b/packages/beacon-node/src/chain/initState.ts index a0bd5c4968a1..20a2188136b5 100644 --- a/packages/beacon-node/src/chain/initState.ts +++ b/packages/beacon-node/src/chain/initState.ts @@ -87,7 +87,7 @@ export async function initStateFromEth1({ const builder = new GenesisBuilder({ config, - eth1Provider: new Eth1Provider(config, opts, signal), + eth1Provider: new Eth1Provider(config, {...opts, logger}, signal), logger, signal, pendingStatus: diff --git a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts index 92b922cbf9f5..d79a98b21745 100644 --- a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts +++ b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts @@ -176,17 +176,16 @@ export class Eth1DepositDataTracker { await sleep(sleepTimeMs, this.signal); } } catch (e) { + this.metrics?.eth1.depositTrackerUpdateErrors.inc(1); + // From Infura: 429 Too Many Requests if (e instanceof HttpRpcError && e.status === 429) { this.logger.debug("Eth1 provider rate limited", {}, e); await sleep(RATE_LIMITED_WAIT_MS, this.signal); // only log error if state switched from online to some other state } else if (!isErrorAborted(e) && this.eth1Provider.getState() !== oldState) { - this.logger.error("Error updating eth1 chain cache", {}, e as Error); await sleep(MIN_WAIT_ON_ERROR_MS, this.signal); } - - this.metrics?.eth1.depositTrackerUpdateErrors.inc(1); } } } diff --git a/packages/beacon-node/src/eth1/index.ts b/packages/beacon-node/src/eth1/index.ts index f53af42ff6a3..9fdba90258a2 100644 --- a/packages/beacon-node/src/eth1/index.ts +++ b/packages/beacon-node/src/eth1/index.ts @@ -67,7 +67,13 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { modules: Eth1DepositDataTrackerModules & Eth1MergeBlockTrackerModules & {eth1Provider?: IEth1Provider} ) { const eth1Provider = - modules.eth1Provider || new Eth1Provider(modules.config, opts, modules.signal, modules.metrics?.eth1HttpClient); + modules.eth1Provider || + new Eth1Provider( + modules.config, + {...opts, logger: modules.logger}, + modules.signal, + modules.metrics?.eth1HttpClient + ); this.eth1DepositDataTracker = opts.disableEth1DepositDataTracker ? null diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index ebc1e24427ce..06773e382c77 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -1,7 +1,8 @@ import {toHexString} from "@chainsafe/ssz"; import {phase0} from "@lodestar/types"; import {ChainConfig} from "@lodestar/config"; -import {fromHex, isErrorAborted} from "@lodestar/utils"; +import {fromHex, isErrorAborted, waitForElapsedTime} from "@lodestar/utils"; +import {Logger} from "@lodestar/logger"; import {FetchError, isFetchError} from "@lodestar/api"; import {linspace} from "../../util/numpy.js"; @@ -51,19 +52,23 @@ const getBlockByHashOpts: ReqOpts = {routeId: "getBlockByHash"}; const getBlockNumberOpts: ReqOpts = {routeId: "getBlockNumber"}; const getLogsOpts: ReqOpts = {routeId: "getLogs"}; +const ifOneMinutePassed = waitForElapsedTime({minElapsedTime: 60_000}); + export class Eth1Provider implements IEth1Provider { readonly deployBlock: number; private readonly depositContractAddress: string; private readonly rpc: JsonRpcHttpClient; // The default state is ONLINE, it will be updated to offline if we receive a http error private state: Eth1ProviderState = Eth1ProviderState.ONLINE; + private logger: Logger; constructor( config: Pick, - opts: Pick, + opts: Pick & {logger: Logger}, signal?: AbortSignal, metrics?: JsonRpcHttpClientMetrics | null ) { + this.logger = opts.logger; this.deployBlock = opts.depositContractDeployBlock ?? 0; this.depositContractAddress = toHexString(config.DEPOSIT_CONTRACT_ADDRESS); this.rpc = new JsonRpcHttpClient(opts.providerUrls ?? DEFAULT_PROVIDER_URLS, { @@ -75,28 +80,36 @@ export class Eth1Provider implements IEth1Provider { }); this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => { + const oldState = this.state; this.state = Eth1ProviderState.ONLINE; + + if (oldState !== Eth1ProviderState.ONLINE) { + this.logger.info("Eth1Provider is back online", {oldState, newState: this.state}); + } }); this.rpc.emitter.on(JsonRpcHttpClientEvent.ERROR, ({error}) => { if (isErrorAborted(error)) { this.state = Eth1ProviderState.ONLINE; - return; - } - - if ((error as unknown) instanceof HttpRpcError || (error as unknown) instanceof ErrorJsonRpcResponse) { + } else if ((error as unknown) instanceof HttpRpcError || (error as unknown) instanceof ErrorJsonRpcResponse) { this.state = Eth1ProviderState.ERROR; - return; - } - - if (error && isFetchError(error) && HTTP_FATAL_ERROR_CODES.includes((error as FetchError).code)) { + } else if (error && isFetchError(error) && HTTP_FATAL_ERROR_CODES.includes((error as FetchError).code)) { this.state = Eth1ProviderState.OFFLINE; - return; + } else if (error && isFetchError(error) && HTTP_CONNECTION_ERROR_CODES.includes((error as FetchError).code)) { + this.state = Eth1ProviderState.AUTH_FAILED; } - if (error && isFetchError(error) && HTTP_CONNECTION_ERROR_CODES.includes((error as FetchError).code)) { - this.state = Eth1ProviderState.AUTH_FAILED; - return; + if (this.state !== Eth1ProviderState.ONLINE) { + ifOneMinutePassed(({msSinceLastCall, now}) => { + this.logger.error( + "Eth1Provider faced error", + { + state: this.state, + lastErrorAt: msSinceLastCall !== undefined ? new Date(now - msSinceLastCall).toLocaleTimeString() : "N/A", + }, + error + ); + }); } }); } From 1676e9ba0e97382f7ecff2c0a38e87382e3d09a0 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 28 Sep 2023 17:39:45 +0200 Subject: [PATCH 06/10] Fix the typs --- packages/beacon-node/src/eth1/provider/eth1Provider.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index 06773e382c77..1cada48f4dee 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -60,11 +60,11 @@ export class Eth1Provider implements IEth1Provider { private readonly rpc: JsonRpcHttpClient; // The default state is ONLINE, it will be updated to offline if we receive a http error private state: Eth1ProviderState = Eth1ProviderState.ONLINE; - private logger: Logger; + private logger?: Logger; constructor( config: Pick, - opts: Pick & {logger: Logger}, + opts: Pick & {logger?: Logger}, signal?: AbortSignal, metrics?: JsonRpcHttpClientMetrics | null ) { @@ -84,7 +84,7 @@ export class Eth1Provider implements IEth1Provider { this.state = Eth1ProviderState.ONLINE; if (oldState !== Eth1ProviderState.ONLINE) { - this.logger.info("Eth1Provider is back online", {oldState, newState: this.state}); + this.logger?.info("Eth1Provider is back online", {oldState, newState: this.state}); } }); @@ -101,7 +101,7 @@ export class Eth1Provider implements IEth1Provider { if (this.state !== Eth1ProviderState.ONLINE) { ifOneMinutePassed(({msSinceLastCall, now}) => { - this.logger.error( + this.logger?.error( "Eth1Provider faced error", { state: this.state, From f309f2882cd259bdb782267363af9ef5ca2c85ee Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 29 Sep 2023 14:30:29 +0200 Subject: [PATCH 07/10] Fix the condition for the error tracker --- packages/beacon-node/src/eth1/eth1DepositDataTracker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts index d79a98b21745..3e695e6d6c57 100644 --- a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts +++ b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts @@ -183,7 +183,7 @@ export class Eth1DepositDataTracker { this.logger.debug("Eth1 provider rate limited", {}, e); await sleep(RATE_LIMITED_WAIT_MS, this.signal); // only log error if state switched from online to some other state - } else if (!isErrorAborted(e) && this.eth1Provider.getState() !== oldState) { + } else if (!isErrorAborted(e)) { await sleep(MIN_WAIT_ON_ERROR_MS, this.signal); } } From ac8c625e251ba6c6af4b017c905aa2ddd9efe203 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 29 Sep 2023 14:54:57 +0200 Subject: [PATCH 08/10] Simplify the waitForElapsedTime --- .../src/eth1/provider/eth1Provider.ts | 8 ++-- packages/utils/src/waitFor.ts | 40 ++++++---------- packages/utils/test/unit/waitFor.test.ts | 46 ++++--------------- 3 files changed, 27 insertions(+), 67 deletions(-) diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index 1cada48f4dee..64387be36312 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -52,7 +52,7 @@ const getBlockByHashOpts: ReqOpts = {routeId: "getBlockByHash"}; const getBlockNumberOpts: ReqOpts = {routeId: "getBlockNumber"}; const getLogsOpts: ReqOpts = {routeId: "getLogs"}; -const ifOneMinutePassed = waitForElapsedTime({minElapsedTime: 60_000}); +const isOneMinutePassed = waitForElapsedTime({minElapsedTime: 60_000}); export class Eth1Provider implements IEth1Provider { readonly deployBlock: number; @@ -100,16 +100,16 @@ export class Eth1Provider implements IEth1Provider { } if (this.state !== Eth1ProviderState.ONLINE) { - ifOneMinutePassed(({msSinceLastCall, now}) => { + if (isOneMinutePassed()) { this.logger?.error( "Eth1Provider faced error", { state: this.state, - lastErrorAt: msSinceLastCall !== undefined ? new Date(now - msSinceLastCall).toLocaleTimeString() : "N/A", + lastErrorAt: new Date(Date.now() - isOneMinutePassed.msSinceLastCall).toLocaleTimeString(), }, error ); - }); + } } }); } diff --git a/packages/utils/src/waitFor.ts b/packages/utils/src/waitFor.ts index 04d0b0fa04b6..12dccb36e2c7 100644 --- a/packages/utils/src/waitFor.ts +++ b/packages/utils/src/waitFor.ts @@ -52,44 +52,32 @@ export function waitFor(condition: () => boolean, opts: WaitForOpts = {}): Promi }); } -interface ElapsedTimeTrackerAttributes { - msSinceLastError?: number; - msSinceLastCall?: number; - now: number; +export interface ElapsedTimeTracker { + (): boolean; + msSinceLastCall: number; } -interface ElapsedTimeTrackerOptions { - minElapsedTime: number; - onError?: (data: ElapsedTimeTrackerAttributes) => void; -} - -type ElapsedTimeTrackerCaller = (data: ElapsedTimeTrackerAttributes) => void; -export type ElapsedTimeTracker = (fn: ElapsedTimeTrackerCaller) => void; - /** * Create a tracker which keeps track of the last time a function was called * * @param durationMs * @returns */ -export function waitForElapsedTime({minElapsedTime, onError}: ElapsedTimeTrackerOptions): ElapsedTimeTracker { +export function waitForElapsedTime({minElapsedTime}: {minElapsedTime: number}): ElapsedTimeTracker { // Initialized with undefined as the function has not been called yet let lastTimeCalled: number | undefined = undefined; - let lastTimeError: number | undefined = undefined; - return function elapsedTimeTracker(fn: ElapsedTimeTrackerCaller): void { + function elapsedTimeTracker(): boolean { const now = Date.now(); + const msSinceLastCall = now - (lastTimeCalled ?? 0); + lastTimeCalled = now; - const msSinceLastCall = lastTimeCalled === undefined ? undefined : now - lastTimeCalled; - const msSinceLastError = lastTimeError === undefined ? undefined : now - lastTimeError; - - if (msSinceLastCall !== undefined && msSinceLastCall < minElapsedTime) { - if (onError) onError({now, msSinceLastError, msSinceLastCall}); - lastTimeError = now; - return; - } + return msSinceLastCall > minElapsedTime; + } - fn({now, msSinceLastCall, msSinceLastError}); - lastTimeCalled = now; - }; + return Object.assign(elapsedTimeTracker, { + get msSinceLastCall() { + return Date.now() - (lastTimeCalled ?? 0); + }, + }); } diff --git a/packages/utils/test/unit/waitFor.test.ts b/packages/utils/test/unit/waitFor.test.ts index da5a1fec1bba..1560445db5d5 100644 --- a/packages/utils/test/unit/waitFor.test.ts +++ b/packages/utils/test/unit/waitFor.test.ts @@ -1,6 +1,5 @@ import "../setup.js"; import {expect} from "chai"; -import sinon from "sinon"; import {waitFor, waitForElapsedTime} from "../../src/waitFor.js"; import {ErrorAborted, TimeoutError} from "../../src/errors.js"; import {sleep} from "../../src/sleep.js"; @@ -39,54 +38,27 @@ describe("waitFor", () => { }); describe("waitForElapsedTime", () => { - it("should call the function for the first time", () => { + it("should true for the first time", () => { const callIfTimePassed = waitForElapsedTime({minElapsedTime: 1000}); - const fn = sinon.spy(); - callIfTimePassed(fn); - expect(fn).to.have.been.calledOnce; + expect(callIfTimePassed()).to.be.true; }); - it("should call the function after the minElapsedTime has passed", async () => { + it("should return true after the minElapsedTime has passed", async () => { const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100}); - const fn = sinon.spy(); - callIfTimePassed(fn); + callIfTimePassed(); await sleep(150); - callIfTimePassed(fn); - expect(fn).to.have.been.calledTwice; + expect(callIfTimePassed()).to.be.true; }); - it("should not call the function before the minElapsedTime has passed", () => { + it("should return false before the minElapsedTime has passed", async () => { const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100}); - const fn = sinon.spy(); - callIfTimePassed(fn); - callIfTimePassed(fn); + callIfTimePassed(); - expect(fn).to.have.been.calledOnce; - }); - - it("should call the onError if the function is called before the minElapsedTime has passed", () => { - const fn = sinon.spy(); - const err = sinon.spy(); - const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100, onError: err}); - callIfTimePassed(fn); - callIfTimePassed(fn); - - expect(err).to.have.been.calledOnce; - }); - - it("should not call the onError if the function is called after the minElapsedTime has passed", async () => { - const fn = sinon.spy(); - const err = sinon.spy(); - const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100, onError: err}); - callIfTimePassed(fn); - - await sleep(100); - - callIfTimePassed(fn); + await sleep(10); - expect(err).to.have.been.callCount(0); + expect(callIfTimePassed()).to.be.false; }); }); From e3e9630eb101d93cb19361d62436849e9e45a588 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 29 Sep 2023 14:59:26 +0200 Subject: [PATCH 09/10] Update the name for tracker --- packages/beacon-node/src/eth1/provider/eth1Provider.ts | 4 ++-- packages/utils/src/waitFor.ts | 2 +- packages/utils/test/unit/waitFor.test.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index 64387be36312..eb8f37d37489 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {phase0} from "@lodestar/types"; import {ChainConfig} from "@lodestar/config"; -import {fromHex, isErrorAborted, waitForElapsedTime} from "@lodestar/utils"; +import {fromHex, isErrorAborted, createElapsedTimeTracker} from "@lodestar/utils"; import {Logger} from "@lodestar/logger"; import {FetchError, isFetchError} from "@lodestar/api"; @@ -52,7 +52,7 @@ const getBlockByHashOpts: ReqOpts = {routeId: "getBlockByHash"}; const getBlockNumberOpts: ReqOpts = {routeId: "getBlockNumber"}; const getLogsOpts: ReqOpts = {routeId: "getLogs"}; -const isOneMinutePassed = waitForElapsedTime({minElapsedTime: 60_000}); +const isOneMinutePassed = createElapsedTimeTracker({minElapsedTime: 60_000}); export class Eth1Provider implements IEth1Provider { readonly deployBlock: number; diff --git a/packages/utils/src/waitFor.ts b/packages/utils/src/waitFor.ts index 12dccb36e2c7..ea4fdf91b9ed 100644 --- a/packages/utils/src/waitFor.ts +++ b/packages/utils/src/waitFor.ts @@ -63,7 +63,7 @@ export interface ElapsedTimeTracker { * @param durationMs * @returns */ -export function waitForElapsedTime({minElapsedTime}: {minElapsedTime: number}): ElapsedTimeTracker { +export function createElapsedTimeTracker({minElapsedTime}: {minElapsedTime: number}): ElapsedTimeTracker { // Initialized with undefined as the function has not been called yet let lastTimeCalled: number | undefined = undefined; diff --git a/packages/utils/test/unit/waitFor.test.ts b/packages/utils/test/unit/waitFor.test.ts index 1560445db5d5..d659be3d4bcb 100644 --- a/packages/utils/test/unit/waitFor.test.ts +++ b/packages/utils/test/unit/waitFor.test.ts @@ -1,6 +1,6 @@ import "../setup.js"; import {expect} from "chai"; -import {waitFor, waitForElapsedTime} from "../../src/waitFor.js"; +import {waitFor, createElapsedTimeTracker} from "../../src/waitFor.js"; import {ErrorAborted, TimeoutError} from "../../src/errors.js"; import {sleep} from "../../src/sleep.js"; @@ -39,13 +39,13 @@ describe("waitFor", () => { describe("waitForElapsedTime", () => { it("should true for the first time", () => { - const callIfTimePassed = waitForElapsedTime({minElapsedTime: 1000}); + const callIfTimePassed = createElapsedTimeTracker({minElapsedTime: 1000}); expect(callIfTimePassed()).to.be.true; }); it("should return true after the minElapsedTime has passed", async () => { - const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100}); + const callIfTimePassed = createElapsedTimeTracker({minElapsedTime: 100}); callIfTimePassed(); await sleep(150); @@ -54,7 +54,7 @@ describe("waitForElapsedTime", () => { }); it("should return false before the minElapsedTime has passed", async () => { - const callIfTimePassed = waitForElapsedTime({minElapsedTime: 100}); + const callIfTimePassed = createElapsedTimeTracker({minElapsedTime: 100}); callIfTimePassed(); await sleep(10); From cd1d58231859c06d1e0600e79b59610de5b6cf1b Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 29 Sep 2023 15:21:34 +0200 Subject: [PATCH 10/10] Fix lint error --- packages/beacon-node/src/eth1/eth1DepositDataTracker.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts index 3e695e6d6c57..f36f70abbbc4 100644 --- a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts +++ b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts @@ -164,7 +164,6 @@ export class Eth1DepositDataTracker { while (!this.signal.aborted) { lastRunMs = Date.now(); - const oldState = this.eth1Provider.getState(); try { const hasCaughtUp = await this.update();