Skip to content

Commit eced1b7

Browse files
authored
Cache results from resolveEntityType (#862)
* Add cache Signed-off-by: nikolay <[email protected]> * Fix mirror node client tests Signed-off-by: nikolay <[email protected]> * Edit eth test Signed-off-by: nikolay <[email protected]> * Pull main Signed-off-by: nikolay <[email protected]> * Add hollow account test Signed-off-by: nikolay <[email protected]> * Bump the local node version Signed-off-by: nikolay <[email protected]> --------- Signed-off-by: nikolay <[email protected]>
1 parent ac5a530 commit eced1b7

File tree

8 files changed

+13837
-23290
lines changed

8 files changed

+13837
-23290
lines changed

package-lock.json

Lines changed: 13764 additions & 23264 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"check:node": "ts-node packages/server/tests/helpers/nodeCheck.ts"
4949
},
5050
"dependencies": {
51-
"@hashgraph/hedera-local": "^2.4.3",
51+
"@hashgraph/hedera-local": "^2.4.5",
5252
"@open-rpc/schema-utils-js": "^1.16.1",
5353
"@types/find-config": "^1.0.1",
5454
"keyv-file": "^0.2.0",

packages/relay/src/lib/clients/mirrorNodeClient.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import constants from './../constants';
2525
import { Histogram, Registry } from 'prom-client';
2626
import { formatRequestIdMessage } from '../../formatters';
2727
import axiosRetry from 'axios-retry';
28+
const LRU = require('lru-cache');
2829

2930
type REQUEST_METHODS = 'GET' | 'POST';
3031

@@ -104,6 +105,8 @@ export class MirrorNodeClient {
104105

105106
private mirrorResponseHistogram;
106107

108+
private readonly cache;
109+
107110
protected createAxiosClient(
108111
baseUrl: string
109112
): AxiosInstance {
@@ -131,7 +134,7 @@ export class MirrorNodeClient {
131134
},
132135
shouldResetTimeout: true
133136
});
134-
137+
135138
return axiosClient;
136139
}
137140

@@ -168,8 +171,9 @@ export class MirrorNodeClient {
168171
});
169172

170173
this.logger.info(`Mirror Node client successfully configured to REST url: ${this.restUrl} and Web3 url: ${this.web3Url} `);
174+
this.cache = new LRU({ max: constants.CACHE_MAX, ttl: constants.CACHE_TTL.ONE_HOUR });
171175
}
172-
176+
173177
private buildUrl(baseUrl: string) {
174178
if (!baseUrl.match(/^https?:\/\//)) {
175179
baseUrl = `https://${baseUrl}`;
@@ -547,6 +551,12 @@ export class MirrorNodeClient {
547551
requestId?: string,
548552
searchableTypes: any[] = [constants.TYPE_CONTRACT, constants.TYPE_ACCOUNT, constants.TYPE_TOKEN]
549553
) {
554+
const cachedLabel = `resolveEntityType.${entityIdentifier}`;
555+
const cachedResponse: { type: string, entity: any } | undefined = this.cache.get(cachedLabel);
556+
if (cachedResponse != undefined) {
557+
return cachedResponse;
558+
}
559+
550560
const buildPromise = (fn) => new Promise((resolve, reject) => fn.then((values) => {
551561
if (values == null) reject();
552562
resolve(values);
@@ -555,10 +565,12 @@ export class MirrorNodeClient {
555565
if (searchableTypes.find(t => t === constants.TYPE_CONTRACT)) {
556566
const contract = await this.getContract(entityIdentifier, requestId);
557567
if (contract) {
558-
return {
568+
const response = {
559569
type: constants.TYPE_CONTRACT,
560570
entity: contract
561571
};
572+
this.cache.set(cachedLabel, response);
573+
return response;
562574
}
563575
}
564576

@@ -589,10 +601,12 @@ export class MirrorNodeClient {
589601
}
590602
}
591603

592-
return {
604+
const response = {
593605
type,
594606
entity: data.value
595607
};
608+
this.cache.set(cachedLabel, response);
609+
return response;
596610
}
597611

598612
//exposing mirror node instance for tests

packages/relay/tests/helpers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import { expect } from "chai";
2222
import { ethers } from 'ethers';
23+
import crypto from 'crypto';
2324

2425
// Randomly generated key
2526
const defaultPrivateKey = '8841e004c6f47af679c91d9282adc62aeb9fabd19cdff6a9da5a358d0613c30a';
@@ -42,6 +43,10 @@ const signTransaction = async (transaction, key = defaultPrivateKey) => {
4243
return wallet.signTransaction(transaction);
4344
};
4445

46+
const random20BytesAddress = (addHexPrefix = true) => {
47+
return (addHexPrefix ? '0x' : '') + crypto.randomBytes(20).toString('hex');
48+
};
49+
4550
const mockData = {
4651
accountEvmAddress: '0x00000000000000000000000000000000000003f6',
4752
account: {
@@ -164,7 +169,7 @@ const mockData = {
164169
}
165170
};
166171

167-
export { expectUnsupportedMethod, expectedError, signTransaction, mockData };
172+
export { expectUnsupportedMethod, expectedError, signTransaction, mockData, random20BytesAddress };
168173

169174
export const bytecode = '0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100329190';
170175
export const blockHashTrimmed = '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b';

packages/relay/tests/lib/eth.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ describe('Eth calls using MirrorNode', async function () {
136136
const contractHash2 = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393';
137137
const contractHash3 = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394';
138138
const contractAddress2 = '0x000000000000000000000000000000000000055e';
139+
const contractAddress3 = '0x000000000000000000000000000000000000255c';
139140
const wrongContractAddress = '0x00000000000000000000000000000000055e';
140141
const contractTimestamp2 = '1653077542.701408897';
141142
const contractTimestamp3 = '1653088542.123456789';
@@ -1548,23 +1549,23 @@ describe('Eth calls using MirrorNode', async function () {
15481549
});
15491550

15501551
it('should return the bytecode from SDK if Mirror Node returns 404', async () => {
1551-
restMock.onGet(`contracts/${contractAddress1}`).reply(404, defaultContract);
1552-
restMock.onGet(`accounts/${contractAddress1}`).reply(404, null);
1553-
restMock.onGet(`tokens/0.0.${parseInt(contractAddress1, 16)}`).reply(404, null);
1552+
restMock.onGet(`contracts/${contractAddress2}`).reply(404, defaultContract);
1553+
restMock.onGet(`accounts/${contractAddress2}`).reply(404, null);
1554+
restMock.onGet(`tokens/0.0.${parseInt(contractAddress2, 16)}`).reply(404, null);
15541555
sdkClientStub.getContractByteCode.returns(Buffer.from(deployedBytecode.replace('0x', ''), 'hex'));
1555-
const res = await ethImpl.getCode(contractAddress1, null);
1556+
const res = await ethImpl.getCode(contractAddress2, null);
15561557
expect(res).to.equal(deployedBytecode);
15571558
});
15581559

15591560
it('should return the bytecode from SDK if Mirror Node returns empty runtime_bytecode', async () => {
1560-
restMock.onGet(`contracts/${contractAddress1}`).reply(404, {
1561+
restMock.onGet(`contracts/${contractAddress3}`).reply(404, {
15611562
...defaultContract,
15621563
runtime_bytecode: EthImpl.emptyHex
15631564
});
1564-
restMock.onGet(`accounts/${contractAddress1}`).reply(404, null);
1565-
restMock.onGet(`tokens/0.0.${parseInt(contractAddress1, 16)}`).reply(404, null);
1565+
restMock.onGet(`accounts/${contractAddress3}`).reply(404, null);
1566+
restMock.onGet(`tokens/0.0.${parseInt(contractAddress3, 16)}`).reply(404, null);
15661567
sdkClientStub.getContractByteCode.returns(Buffer.from(deployedBytecode.replace('0x', ''), 'hex'));
1567-
const res = await ethImpl.getCode(contractAddress1, null);
1568+
const res = await ethImpl.getCode(contractAddress3, null);
15681569
expect(res).to.equal(deployedBytecode);
15691570
});
15701571

packages/relay/tests/lib/mirrorNodeClient.spec.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient';
2727
import constants from '../../src/lib/constants';
2828
import axios from 'axios';
2929
import MockAdapter from 'axios-mock-adapter';
30-
import {mockData} from './../helpers';
30+
import {mockData, random20BytesAddress} from './../helpers';
3131
const registry = new Registry();
3232

3333
import pino from 'pino';
@@ -605,10 +605,11 @@ describe('MirrorNodeClient', async function () {
605605
});
606606

607607
describe('resolveEntityType', async () => {
608+
const notFoundAddress = random20BytesAddress();
608609
it('returns `contract` when CONTRACTS endpoint returns a result', async() => {
609610
mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(200, mockData.contract);
610611
mock.onGet(`accounts/${mockData.contractEvmAddress}`).reply(200, mockData.account);
611-
mock.onGet(`tokens/${mockData.tokenId}`).reply(404, mockData.notFound);
612+
mock.onGet(`tokens/${mockData.contractEvmAddress}`).reply(404, mockData.notFound);
612613

613614
const entityType = await mirrorNodeInstance.resolveEntityType(mockData.contractEvmAddress);
614615
expect(entityType).to.exist;
@@ -620,11 +621,11 @@ describe('MirrorNodeClient', async function () {
620621
});
621622

622623
it('returns `account` when CONTRACTS and TOKENS endpoint returns 404 and ACCOUNTS endpoint returns a result', async() => {
623-
mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(404, mockData.notFound);
624-
mock.onGet(`accounts/${mockData.contractEvmAddress}`).reply(200, mockData.account);
624+
mock.onGet(`contracts/${mockData.accountEvmAddress}`).reply(404, mockData.notFound);
625+
mock.onGet(`accounts/${mockData.accountEvmAddress}`).reply(200, mockData.account);
625626
mock.onGet(`tokens/${mockData.tokenId}`).reply(404, mockData.notFound);
626627

627-
const entityType = await mirrorNodeInstance.resolveEntityType(mockData.contractEvmAddress);
628+
const entityType = await mirrorNodeInstance.resolveEntityType(mockData.accountEvmAddress);
628629
expect(entityType).to.exist;
629630
expect(entityType).to.have.property('type');
630631
expect(entityType).to.have.property('entity');
@@ -634,8 +635,8 @@ describe('MirrorNodeClient', async function () {
634635
});
635636

636637
it('returns `token` when CONTRACTS and ACCOUNTS endpoints returns 404 and TOKEN endpoint returns a result', async() => {
637-
mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(404, mockData.notFound);
638-
mock.onGet(`accounts/${mockData.contractEvmAddress}`).reply(404, mockData.notFound);
638+
mock.onGet(`contracts/${notFoundAddress}`).reply(404, mockData.notFound);
639+
mock.onGet(`accounts/${notFoundAddress}`).reply(404, mockData.notFound);
639640
mock.onGet(`tokens/${mockData.tokenId}`).reply(200, mockData.token);
640641

641642
const entityType = await mirrorNodeInstance.resolveEntityType(mockData.tokenLongZero);
@@ -647,11 +648,11 @@ describe('MirrorNodeClient', async function () {
647648
});
648649

649650
it('returns null when CONTRACTS and ACCOUNTS endpoints return 404', async() => {
650-
mock.onGet(`contracts/${mockData.contractEvmAddress}`).reply(404, mockData.notFound);
651-
mock.onGet(`accounts/${mockData.contractEvmAddress}`).reply(404, mockData.notFound);
652-
mock.onGet(`tokens/${mockData.tokenId}`).reply(404, mockData.notFound);
651+
mock.onGet(`contracts/${notFoundAddress}`).reply(404, mockData.notFound);
652+
mock.onGet(`accounts/${notFoundAddress}`).reply(404, mockData.notFound);
653+
mock.onGet(`tokens/${notFoundAddress}`).reply(404, mockData.notFound);
653654

654-
const entityType = await mirrorNodeInstance.resolveEntityType(mockData.contractEvmAddress);
655+
const entityType = await mirrorNodeInstance.resolveEntityType(notFoundAddress);
655656
expect(entityType).to.be.null;
656657
});
657658
});

packages/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"pino-pretty": "^7.6.1"
2121
},
2222
"devDependencies": {
23-
"@hashgraph/hedera-local": "^2.4.3",
23+
"@hashgraph/hedera-local": "^2.4.5",
2424
"@hashgraph/sdk": "^2.19.2",
2525
"@koa/cors": "^3.1.0",
2626
"@types/chai": "^4.3.0",

packages/server/tests/acceptance/rpc_batch1.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,32 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
856856
expect(res).to.be.equal('0x0');
857857
});
858858

859+
it('should execute "eth_getTransactionCount" from hollow account', async function() {
860+
const hollowAccount = ethers.Wallet.createRandom();
861+
const resBeforeCreation = await relay.call('eth_getTransactionCount', [hollowAccount.address, 'latest'], requestId);
862+
expect(resBeforeCreation).to.be.equal('0x0');
863+
864+
const signedTxHollowAccountCreation = await accounts[2].wallet.signTransaction({
865+
...defaultLondonTransactionData,
866+
value: '5000000000000000000', // 5 HBARs
867+
to: hollowAccount.address,
868+
nonce: await relay.getAccountNonce('0x' + accounts[2].address, requestId)
869+
});
870+
const txHashHAC = await relay.call('eth_sendRawTransaction', [signedTxHollowAccountCreation], requestId);
871+
await mirrorNode.get(`/contracts/results/${txHashHAC}`, requestId);
872+
873+
const signTxFromHollowAccount = await hollowAccount.signTransaction({
874+
...defaultLondonTransactionData,
875+
to: mirrorContract.evm_address,
876+
nonce: await relay.getAccountNonce(hollowAccount.address, requestId)
877+
});
878+
const txHashHA = await relay.call('eth_sendRawTransaction', [signTxFromHollowAccount], requestId);
879+
await mirrorNode.get(`/contracts/results/${txHashHA}`, requestId);
880+
881+
const resAfterCreation = await relay.call('eth_getTransactionCount', [hollowAccount.address, 'latest'], requestId);
882+
expect(resAfterCreation).to.be.equal('0x1');
883+
});
884+
859885
it('should execute "eth_getTransactionCount" for account with non-zero nonce', async function () {
860886
const account = await servicesNode.createAliasAccount(10, null, requestId);
861887
// Wait for account creation to propagate

0 commit comments

Comments
 (0)