diff --git a/lib/utils/ens-cache.js b/lib/utils/ens-cache.js --- a/lib/utils/ens-cache.js +++ b/lib/utils/ens-cache.js @@ -60,12 +60,12 @@ // If we fail to find an ENS name for an address, fail to confirm a matching // forward resolution, or if the ENS name does not equal its normalized // version, we will return undefined. - async getNameForAddress(ethAddress: string): Promise { + getNameForAddress(ethAddress: string): Promise { const normalizedETHAddress = normalizeETHAddress(ethAddress); - const cacheResult = this.getCachedNameForAddress(normalizedETHAddress); + const cacheResult = this.getCachedEntryForAddress(normalizedETHAddress); if (cacheResult) { - return cacheResult; + return Promise.resolve(cacheResult.normalizedENSName); } const fetchENSNamePromise = (async () => { @@ -83,13 +83,21 @@ return normalizedENSName; })(); - const normalizedENSName = await fetchENSNamePromise; this.nameQueryCache.set(normalizedETHAddress, { normalizedETHAddress, cacheInsertionTime: Date.now(), - normalizedENSName, + normalizedENSName: fetchENSNamePromise, }); - return normalizedENSName; + + return (async () => { + const normalizedENSName = await fetchENSNamePromise; + this.nameQueryCache.set(normalizedETHAddress, { + normalizedETHAddress, + cacheInsertionTime: Date.now(), + normalizedENSName, + }); + return normalizedENSName; + })(); } getCachedEntryForAddress(ethAddress: string): ?ENSNameQueryCacheEntry { @@ -123,15 +131,15 @@ return normalizedENSName; } - async getAddressForName(ensName: string): Promise { + getAddressForName(ensName: string): Promise { const normalizedENSName = normalizeENSName(ensName); if (normalizedENSName !== ensName) { - return undefined; + return Promise.resolve(undefined); } - const cacheResult = this.getCachedAddressForName(normalizedENSName); + const cacheResult = this.getCachedEntryForName(normalizedENSName); if (cacheResult) { - return cacheResult; + return Promise.resolve(cacheResult.normalizedETHAddress); } const fetchETHAddressPromise = (async () => { @@ -142,13 +150,21 @@ return normalizeETHAddress(ethAddress); })(); - const normalizedETHAddress = await fetchETHAddressPromise; this.addressQueryCache.set(normalizedENSName, { normalizedENSName, cacheInsertionTime: Date.now(), - normalizedETHAddress, + normalizedETHAddress: fetchETHAddressPromise, }); - return normalizedETHAddress; + + return (async () => { + const normalizedETHAddress = await fetchETHAddressPromise; + this.addressQueryCache.set(normalizedENSName, { + normalizedENSName, + cacheInsertionTime: Date.now(), + normalizedETHAddress, + }); + return normalizedETHAddress; + })(); } getCachedEntryForName(ensName: string): ?ENSAddressQueryCacheEntry { @@ -184,6 +200,11 @@ return normalizedETHAddress; } + + clearCache(): void { + this.nameQueryCache = new Map(); + this.addressQueryCache = new Map(); + } } export { ENSCache }; diff --git a/lib/utils/ens-cache.test.js b/lib/utils/ens-cache.test.js --- a/lib/utils/ens-cache.test.js +++ b/lib/utils/ens-cache.test.js @@ -68,6 +68,33 @@ expect(ashoatEthResult).toBe(ashoatDotEth); expect(timesLookupAddressCalled).toBe(timesLookupAddressCalledBefore); }); + it('should dedup simultaneous fetches', async () => { + if (!process.env.ALCHEMY_API_KEY) { + return; + } + + ensCache.clearCache(); + const timesLookupAddressCalledBeforeSingleFetch = timesLookupAddressCalled; + const ashoatEthResult1 = await ensCache.getNameForAddress(ashoatAddr); + expect(ashoatEthResult1).toBe(ashoatDotEth); + const timesLookupAddressCalledForSingleFetch = + timesLookupAddressCalled - timesLookupAddressCalledBeforeSingleFetch; + + ensCache.clearCache(); + const timesLookupAddressCalledBeforeDoubleFetch = timesLookupAddressCalled; + const [ashoatEthResult2, ashoatEthResult3] = await Promise.all([ + ensCache.getNameForAddress(ashoatAddr), + ensCache.getNameForAddress(ashoatAddr.toUpperCase()), + ]); + expect(ashoatEthResult2).toBe(ashoatDotEth); + expect(ashoatEthResult3).toBe(ashoatDotEth); + const timesLookupAddressCalledForDoubleFetch = + timesLookupAddressCalled - timesLookupAddressCalledBeforeDoubleFetch; + + expect(timesLookupAddressCalledForDoubleFetch).toBe( + timesLookupAddressCalledForSingleFetch, + ); + }); }); describe('getAddressForName', () => { @@ -101,4 +128,31 @@ expect(ashoatAddrResult).toBe(ashoatAddr); expect(timesResolveNameCalled).toBe(timesResolveNameCalledBefore); }); + it('should dedup simultaneous fetches', async () => { + if (!process.env.ALCHEMY_API_KEY) { + return; + } + + ensCache.clearCache(); + const timesResolveNameCalledBeforeSingleFetch = timesResolveNameCalled; + const ashoatAddrResult1 = await ensCache.getAddressForName(ashoatDotEth); + expect(ashoatAddrResult1).toBe(ashoatAddr); + const timesResolveNamesCalledForSingleFetch = + timesResolveNameCalled - timesResolveNameCalledBeforeSingleFetch; + + ensCache.clearCache(); + const timesResolveNameCalledBeforeDoubleFetch = timesResolveNameCalled; + const [ashoatAddrResult2, ashoatAddrResult3] = await Promise.all([ + ensCache.getAddressForName(ashoatDotEth), + ensCache.getAddressForName(ashoatDotEth), + ]); + expect(ashoatAddrResult2).toBe(ashoatAddr); + expect(ashoatAddrResult3).toBe(ashoatAddr); + const timesResolveNamesCalledForDoubleFetch = + timesResolveNameCalled - timesResolveNameCalledBeforeDoubleFetch; + + expect(timesResolveNamesCalledForDoubleFetch).toBe( + timesResolveNamesCalledForSingleFetch, + ); + }); });