From 874beb813bc43b90822b6773846b7a48c78349d1 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 28 May 2024 06:16:07 +0000 Subject: [PATCH 1/2] refactor: replace web3 prop with setProvider function If anyone would ever update the provider, they must also update the blockTracker --- src/NonceTracker.ts | 35 +++++++++++++++++++-------- test/nonce-tracker-test.js | 48 +++++++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/NonceTracker.ts b/src/NonceTracker.ts index bf80562..2d72e0f 100644 --- a/src/NonceTracker.ts +++ b/src/NonceTracker.ts @@ -2,12 +2,11 @@ import assert from 'assert'; import { Mutex } from 'async-mutex'; import type { PollingBlockTracker } from '@metamask/eth-block-tracker'; -// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports -const { Web3Provider } = require('@ethersproject/providers'); +import { Web3Provider } from '@ethersproject/providers'; /** * @property opts.provider - An ethereum provider - * @property opts.blockTracker - An instance of eth-block-tracker + * @property opts.blockTracker - An instance of @metamask/eth-block-tracker * @property opts.getPendingTransactions - A function that returns an array of txMeta * whose status is `submitted` * @property opts.getConfirmedTransactions - A function that returns an array of txMeta @@ -93,8 +92,6 @@ export class NonceTracker { private blockTracker: PollingBlockTracker; - private web3: typeof Web3Provider; - private getPendingTransactions: (address: string) => Transaction[]; private getConfirmedTransactions: (address: string) => Transaction[]; @@ -104,12 +101,31 @@ export class NonceTracker { constructor(opts: NonceTrackerOptions) { this.provider = opts.provider; this.blockTracker = opts.blockTracker; - this.web3 = new Web3Provider(opts.provider); this.getPendingTransactions = opts.getPendingTransactions; this.getConfirmedTransactions = opts.getConfirmedTransactions; this.lockMap = {}; } + /** + * Allows changing the provider and block tracker after insrtantiation. As the blockTracker also has a provider, they are updated atomically. + * + * @param opts - new props + * @param opts.provider - An ethereum provider + * @param opts.blockTracker - An instance of @metamask/eth-block-tracker + */ + setProvider({ + provider, + blockTracker, + }: { + provider: Record; + blockTracker: PollingBlockTracker; + }): void { + assert(typeof provider === 'object', 'missing or invalid provider'); + assert(typeof blockTracker === 'object', 'missing or invalid blockTracker'); + this.provider = provider; + this.blockTracker = blockTracker; + } + /** * @returns Promise<{ releaseLock: VoidFunction }> with the key releaseLock (the global mutex) */ @@ -209,10 +225,9 @@ export class NonceTracker { // we need to make sure our base count // and pending count are from the same block const blockNumber = await this.blockTracker.getLatestBlock(); - const baseCount: number = await this.web3.getTransactionCount( - address, - blockNumber, - ); + const baseCount: number = await new Web3Provider( + this.provider, + ).getTransactionCount(address, blockNumber); assert( Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`, diff --git a/test/nonce-tracker-test.js b/test/nonce-tracker-test.js index e0a08f0..149830f 100644 --- a/test/nonce-tracker-test.js +++ b/test/nonce-tracker-test.js @@ -3,8 +3,6 @@ const assert = require('assert'); const { NonceTracker } = require('../dist'); const MockTxGen = require('./lib/mock-tx-gen'); -const providerResultStub = {}; - describe('Nonce Tracker', function () { let nonceTracker, pendingTxs, confirmedTxs; @@ -199,6 +197,30 @@ describe('Nonce Tracker', function () { }); }); + describe('when switching provider', function () { + it('should discard previous nonce tracking', async function () { + const txGen = new MockTxGen(); + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }); + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x07'); + + this.timeout(15000); + nonceTracker.setProvider({ + provider: generateProviderWith('0x22'), + blockTracker: generateBlockTracker(), + }); + this.timeout(15000); + const nonceLock = await nonceTracker.getNonceLock( + '0x7d3517b0d011698406d6e0aed8453f0be2697926', + ); + assert.equal( + nonceLock.nextNonce, + '0x22', + `nonce should be 0x22 got ${nonceLock.nextNonce}`, + ); + await nonceLock.releaseLock(); + }); + }); + describe('when there are some pending nonces below the remote one and some over.', function () { it('should return nonce after those', async function () { const txGen = new MockTxGen(); @@ -299,19 +321,29 @@ describe('Nonce Tracker', function () { }); }); -function generateNonceTrackerWith(pending, confirmed, providerStub = '0x0') { - const getPendingTransactions = () => pending; - const getConfirmedTransactions = () => confirmed; - providerResultStub.result = providerStub; - const provider = { +function generateProviderWith(nonce) { + const providerResultStub = {}; + + providerResultStub.result = nonce; + return { sendAsync: (_, cb) => { cb(undefined, providerResultStub); }, }; - const blockTracker = { +} + +function generateBlockTracker() { + return { getCurrentBlock: () => '0x11b568', getLatestBlock: async () => '0x11b568', }; +} + +function generateNonceTrackerWith(pending, confirmed, providerStub = '0x0') { + const getPendingTransactions = () => pending; + const getConfirmedTransactions = () => confirmed; + const provider = generateProviderWith(providerStub); + const blockTracker = generateBlockTracker(); return new NonceTracker({ provider, blockTracker, From 734129a7aac85c3986430c1d97bfee985af33a4c Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 28 May 2024 06:58:01 +0000 Subject: [PATCH 2/2] fix: make private properties publically inaccessible --- src/NonceTracker.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/NonceTracker.ts b/src/NonceTracker.ts index 2d72e0f..1dee0b7 100644 --- a/src/NonceTracker.ts +++ b/src/NonceTracker.ts @@ -88,22 +88,22 @@ export interface Transaction { } export class NonceTracker { - private provider: Record; + #provider: Record; - private blockTracker: PollingBlockTracker; + #blockTracker: PollingBlockTracker; - private getPendingTransactions: (address: string) => Transaction[]; + readonly #getPendingTransactions: (address: string) => Transaction[]; - private getConfirmedTransactions: (address: string) => Transaction[]; + readonly #getConfirmedTransactions: (address: string) => Transaction[]; - private lockMap: Record; + readonly #lockMap: Record; constructor(opts: NonceTrackerOptions) { - this.provider = opts.provider; - this.blockTracker = opts.blockTracker; - this.getPendingTransactions = opts.getPendingTransactions; - this.getConfirmedTransactions = opts.getConfirmedTransactions; - this.lockMap = {}; + this.#provider = opts.provider; + this.#blockTracker = opts.blockTracker; + this.#getPendingTransactions = opts.getPendingTransactions; + this.#getConfirmedTransactions = opts.getConfirmedTransactions; + this.#lockMap = {}; } /** @@ -122,8 +122,8 @@ export class NonceTracker { }): void { assert(typeof provider === 'object', 'missing or invalid provider'); assert(typeof blockTracker === 'object', 'missing or invalid blockTracker'); - this.provider = provider; - this.blockTracker = blockTracker; + this.#provider = provider; + this.#blockTracker = blockTracker; } /** @@ -160,7 +160,7 @@ export class NonceTracker { highestLocallyConfirmed, ); - const pendingTxs: Transaction[] = this.getPendingTransactions(address); + const pendingTxs: Transaction[] = this.#getPendingTransactions(address); const localNonceResult: HighestContinuousFrom = this._getHighestContinuousFrom(pendingTxs, highestSuggested); @@ -205,10 +205,10 @@ export class NonceTracker { } _lookupMutex(lockId: string): Mutex { - let mutex: Mutex = this.lockMap[lockId]; + let mutex: Mutex = this.#lockMap[lockId]; if (!mutex) { mutex = new Mutex(); - this.lockMap[lockId] = mutex; + this.#lockMap[lockId] = mutex; } return mutex; } @@ -224,9 +224,9 @@ export class NonceTracker { // calculate next nonce // we need to make sure our base count // and pending count are from the same block - const blockNumber = await this.blockTracker.getLatestBlock(); + const blockNumber = await this.#blockTracker.getLatestBlock(); const baseCount: number = await new Web3Provider( - this.provider, + this.#provider, ).getTransactionCount(address, blockNumber); assert( Number.isInteger(baseCount), @@ -246,7 +246,7 @@ export class NonceTracker { */ _getHighestLocallyConfirmed(address: string): number { const confirmedTransactions: Transaction[] = - this.getConfirmedTransactions(address); + this.#getConfirmedTransactions(address); const highest: number = this._getHighestNonce(confirmedTransactions); return Number.isInteger(highest) ? highest + 1 : 0; }