diff --git a/CHANGELOG.md b/CHANGELOG.md index 366dbe4..19101de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Avoid risk of infinte retry loops when fetching new blocks ([#284](https://github.com/MetaMask/eth-block-tracker/pull/284)) ## [11.0.2] ### Fixed diff --git a/src/PollingBlockTracker.ts b/src/PollingBlockTracker.ts index 8ab89b9..2a4bdc5 100644 --- a/src/PollingBlockTracker.ts +++ b/src/PollingBlockTracker.ts @@ -10,8 +10,6 @@ const log = createModuleLogger(projectLogger, 'polling-block-tracker'); const createRandomId = getCreateRandomId(); const sec = 1000; -const calculateSum = (accumulator: number, currentValue: number) => - accumulator + currentValue; const blockTrackerEvents: (string | symbol)[] = ['sync', 'latest']; export interface PollingBlockTrackerOptions { @@ -54,6 +52,10 @@ export class PollingBlockTracker private readonly _setSkipCacheFlag: boolean; + readonly #internalEventListeners: (( + value: string | PromiseLike, + ) => void)[] = []; + constructor(opts: PollingBlockTrackerOptions = {}) { // parse + validate args if (!opts.provider) { @@ -106,9 +108,17 @@ export class PollingBlockTracker return this._currentBlock; } // wait for a new latest block - const latestBlock: string = await new Promise((resolve) => - this.once('latest', resolve), - ); + const latestBlock: string = await new Promise((resolve) => { + const listener = (value: string | PromiseLike) => { + this.#internalEventListeners.splice( + this.#internalEventListeners.indexOf(listener), + 1, + ); + resolve(value); + }; + this.#internalEventListeners.push(listener); + this.once('latest', listener); + }); // return newly set current block return latestBlock; } @@ -179,9 +189,17 @@ export class PollingBlockTracker } private _getBlockTrackerEventCount(): number { - return blockTrackerEvents - .map((eventName) => this.listenerCount(eventName)) - .reduce(calculateSum); + return ( + blockTrackerEvents + .map((eventName) => this.listeners(eventName)) + .flat() + // internal listeners are not included in the count + .filter((listener) => + this.#internalEventListeners.every( + (internalListener) => !Object.is(internalListener, listener), + ), + ).length + ); } private _shouldUseNewBlock(newBlock: string) { diff --git a/src/SubscribeBlockTracker.ts b/src/SubscribeBlockTracker.ts index d81a8ea..eabe35c 100644 --- a/src/SubscribeBlockTracker.ts +++ b/src/SubscribeBlockTracker.ts @@ -9,8 +9,6 @@ const createRandomId = getCreateRandomId(); const sec = 1000; -const calculateSum = (accumulator: number, currentValue: number) => - accumulator + currentValue; const blockTrackerEvents: (string | symbol)[] = ['sync', 'latest']; export interface SubscribeBlockTrackerOptions { @@ -43,6 +41,10 @@ export class SubscribeBlockTracker private _subscriptionId: string | null; + readonly #internalEventListeners: (( + value: string | PromiseLike, + ) => void)[] = []; + constructor(opts: SubscribeBlockTrackerOptions = {}) { // parse + validate args if (!opts.provider) { @@ -91,9 +93,17 @@ export class SubscribeBlockTracker return this._currentBlock; } // wait for a new latest block - const latestBlock: string = await new Promise((resolve) => - this.once('latest', resolve), - ); + const latestBlock: string = await new Promise((resolve) => { + const listener = (value: string | PromiseLike) => { + this.#internalEventListeners.splice( + this.#internalEventListeners.indexOf(listener), + 1, + ); + resolve(value); + }; + this.#internalEventListeners.push(listener); + this.once('latest', listener); + }); // return newly set current block return latestBlock; } @@ -162,9 +172,17 @@ export class SubscribeBlockTracker } private _getBlockTrackerEventCount(): number { - return blockTrackerEvents - .map((eventName) => this.listenerCount(eventName)) - .reduce(calculateSum); + return ( + blockTrackerEvents + .map((eventName) => this.listeners(eventName)) + .flat() + // internal listeners are not included in the count + .filter((listener) => + this.#internalEventListeners.every( + (internalListener) => !Object.is(internalListener, listener), + ), + ).length + ); } private _shouldUseNewBlock(newBlock: string) {