Skip to content

Commit 7863259

Browse files
Pull evm_address for contract logs (#650)
* Pull evm_address for contract logs Signed-off-by: Nana Essilfie-Conduah <[email protected]> * FixgetLogs return, fix tests, add test case for evm address Signed-off-by: Maksim Dimitrov <[email protected]> * Remove console.log Signed-off-by: Maksim Dimitrov <[email protected]> * Cache contract request. Add test Signed-off-by: Maksim Dimitrov <[email protected]> * Add missing mock Signed-off-by: Maksim Dimitrov <[email protected]> * Remove console.log, add expect for cache Signed-off-by: Maksim Dimitrov <[email protected]> * Fix spacing Signed-off-by: Maksim Dimitrov <[email protected]> Signed-off-by: Nana Essilfie-Conduah <[email protected]> Signed-off-by: Maksim Dimitrov <[email protected]> Co-authored-by: Maksim Dimitrov <[email protected]>
1 parent d92f1f2 commit 7863259

File tree

4 files changed

+145
-34
lines changed

4 files changed

+145
-34
lines changed

packages/relay/src/lib/eth.ts

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export class EthImpl implements Eth {
467467

468468
// To save a request to the mirror node for `latest` and `pending` blocks, we directly return null from `getHistoricalBlockResponse`
469469
// But if a block number or `earliest` tag is passed and the mirror node returns `null`, we should throw an error.
470-
if(!EthImpl.blockTagIsLatestOrPending(blockNumberOrTag) && blockResponse == null) {
470+
if (!EthImpl.blockTagIsLatestOrPending(blockNumberOrTag) && blockResponse == null) {
471471
throw predefined.RESOURCE_NOT_FOUND(`block '${blockNumberOrTag}'.`);
472472
}
473473

@@ -478,7 +478,7 @@ export class EthImpl implements Eth {
478478
// retrieve the contract result details
479479
await this.mirrorNodeClient.getContractResultsDetails(address, contractResult.results[0].timestamp)
480480
.then(contractResultDetails => {
481-
if(contractResultDetails?.state_changes != null) {
481+
if (contractResultDetails?.state_changes != null) {
482482
// filter the state changes to match slot and return value
483483
const stateChange = contractResultDetails.state_changes.find(stateChange => stateChange.slot === slot);
484484
result = stateChange.value_written;
@@ -626,7 +626,7 @@ export class EthImpl implements Eth {
626626
const bytecode = await this.sdkClient.getContractByteCode(0, 0, address, EthImpl.ethGetCode, requestId);
627627
return EthImpl.prepend0x(Buffer.from(bytecode).toString('hex'));
628628
} catch (e: any) {
629-
if(e instanceof SDKClientError) {
629+
if (e instanceof SDKClientError) {
630630
// handle INVALID_CONTRACT_ID
631631
if (e.isInvalidContractId()) {
632632
this.logger.debug(`${requestIdPrefix} Unable to find code for contract ${address} in block "${blockNumber}", returning 0x0, err code: ${e.statusCode}`);
@@ -718,7 +718,7 @@ export class EthImpl implements Eth {
718718
.getContractResults({ blockHash: blockHash, transactionIndex: Number(transactionIndex) }, undefined, requestId)
719719
.then((contractResults) => this.getTransactionFromContractResults(contractResults, requestId))
720720
.catch((e: any) => {
721-
if(e instanceof JsonRpcError) {
721+
if (e instanceof JsonRpcError) {
722722
throw e;
723723
}
724724

@@ -749,7 +749,7 @@ export class EthImpl implements Eth {
749749
.getContractResults({ blockNumber: blockNum, transactionIndex: Number(transactionIndex) }, undefined, requestId)
750750
.then((contractResults) => this.getTransactionFromContractResults(contractResults, requestId))
751751
.catch((e: any) => {
752-
if(e instanceof JsonRpcError) {
752+
if (e instanceof JsonRpcError) {
753753
throw e;
754754
}
755755

@@ -927,10 +927,10 @@ export class EthImpl implements Eth {
927927
}
928928

929929
let fromAddress;
930-
if(contractResult.from) {
930+
if (contractResult.from) {
931931
fromAddress = contractResult.from.substring(0, 42);
932932
const accountResult = await this.mirrorNodeClient.getAccount(fromAddress, requestId);
933-
if(accountResult && accountResult.evm_address && accountResult.evm_address.length > 0) {
933+
if (accountResult && accountResult.evm_address && accountResult.evm_address.length > 0) {
934934
fromAddress = accountResult.evm_address.substring(0,42);
935935
}
936936
}
@@ -1107,7 +1107,7 @@ export class EthImpl implements Eth {
11071107
private async getBlock(blockHashOrNumber: string, showDetails: boolean, requestId?: string ): Promise<Block | null> {
11081108
const blockResponse = await this.getHistoricalBlockResponse(blockHashOrNumber, true);
11091109

1110-
if(blockResponse == null) return null;
1110+
if (blockResponse == null) return null;
11111111

11121112
const timestampRange = blockResponse.timestamp;
11131113
const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`];
@@ -1218,7 +1218,7 @@ export class EthImpl implements Eth {
12181218
return this.mirrorNodeClient.getContractResultsByAddressAndTimestamp(to, timestamp, requestId)
12191219
.then(contractResultDetails => {
12201220
// 404 is allowed return code so it's possible for contractResultDetails to be null
1221-
if(contractResultDetails == null) {
1221+
if (contractResultDetails == null) {
12221222
return null;
12231223
} else {
12241224
const rSig = contractResultDetails.r === null ? null : contractResultDetails.r.substring(0, 66);
@@ -1280,7 +1280,7 @@ export class EthImpl implements Eth {
12801280
let fromBlockNum = 0;
12811281
let toBlockNum;
12821282

1283-
if(!fromBlock && !toBlock) {
1283+
if (!fromBlock && !toBlock) {
12841284
const blockResponse = await this.getHistoricalBlockResponse("latest", true, requestId);
12851285
fromBlockNum = parseInt(blockResponse.number);
12861286
toBlockNum = parseInt(blockResponse.number);
@@ -1289,23 +1289,23 @@ export class EthImpl implements Eth {
12891289
params.timestamp = [];
12901290

12911291
const fromBlockResponse = await this.getHistoricalBlockResponse(fromBlock || "latest", true, requestId);
1292-
if(fromBlockResponse != null) {
1292+
if (fromBlockResponse != null) {
12931293
params.timestamp.push(`gte:${fromBlockResponse.timestamp.from}`);
12941294
fromBlockNum = parseInt(fromBlockResponse.number);
12951295
} else {
12961296
return [];
12971297
}
12981298

12991299
const toBlockResponse = await this.getHistoricalBlockResponse(toBlock || "latest", true, requestId);
1300-
if(toBlockResponse != null) {
1300+
if (toBlockResponse != null) {
13011301
params.timestamp.push(`lte:${toBlockResponse.timestamp.to}`);
13021302
toBlockNum = parseInt(toBlockResponse.number);
13031303
}
13041304
}
13051305

13061306
if (fromBlockNum > toBlockNum) {
13071307
return [];
1308-
} else if((toBlockNum - fromBlockNum) > blockRangeLimit) {
1308+
} else if ((toBlockNum - fromBlockNum) > blockRangeLimit) {
13091309
throw predefined.RANGE_TOO_LARGE(blockRangeLimit);
13101310
}
13111311
}
@@ -1328,24 +1328,44 @@ export class EthImpl implements Eth {
13281328
return [];
13291329
}
13301330

1331-
return result.logs.map(log => {
1332-
return new Log({
1333-
address: log.address,
1334-
blockHash: EthImpl.toHash32(log.block_hash),
1335-
blockNumber: EthImpl.numberTo0x(log.block_number),
1336-
data: log.data,
1337-
logIndex: EthImpl.numberTo0x(log.index),
1338-
removed: false,
1339-
topics: log.topics,
1340-
transactionHash: EthImpl.toHash32(log.transaction_hash),
1341-
transactionIndex: EthImpl.numberTo0x(log.transaction_index)
1342-
});
1343-
});
1331+
const logs: Log[] = [];
1332+
for(const log of result.logs) {
1333+
logs.push(
1334+
new Log({
1335+
address: await this.getLogEvmAddress(log.address, requestId) || log.address,
1336+
blockHash: EthImpl.toHash32(log.block_hash),
1337+
blockNumber: EthImpl.numberTo0x(log.block_number),
1338+
data: log.data,
1339+
logIndex: EthImpl.numberTo0x(log.index),
1340+
removed: false,
1341+
topics: log.topics,
1342+
transactionHash: EthImpl.toHash32(log.transaction_hash),
1343+
transactionIndex: EthImpl.numberTo0x(log.transaction_index)
1344+
})
1345+
);
1346+
}
1347+
1348+
return logs;
13441349
}
13451350

13461351
async maxPriorityFeePerGas(requestId?: string): Promise<string> {
13471352
const requestIdPrefix = formatRequestIdMessage(requestId);
13481353
this.logger.trace(`${requestIdPrefix} maxPriorityFeePerGas()`);
13491354
return EthImpl.zeroHex;
13501355
}
1356+
1357+
private async getLogEvmAddress(address: string, requestId: string | undefined) {
1358+
const cachedLabel = `getLogEvmAddress.${address}`;
1359+
let contractAddress = cache.get(cachedLabel);
1360+
1361+
// If contractAddress === undefined it means there's no cache record
1362+
// and a mirror node request should be executed.
1363+
if (contractAddress === undefined) {
1364+
const contract = await this.mirrorNodeClient.getContract(address, requestId);
1365+
contractAddress = contract.evm_address;
1366+
cache.set(cachedLabel, contractAddress, constants.CACHE_TTL.ONE_HOUR);
1367+
}
1368+
1369+
return contractAddress;
1370+
}
13511371
}

packages/relay/tests/helpers.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ const mockData = {
165165

166166
export { expectUnsupportedMethod, expectedError, signTransaction, mockData };
167167

168-
169168
export const bytecode = '0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100329190';
170169
export const blockHashTrimmed = '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b';
171170
export const blockHash = `${blockHashTrimmed}999fc7e86699f60f2a3fb3ed9a646c6b`;
@@ -207,6 +206,28 @@ export const defaultBlock = {
207206
'gas_used': gasUsed1 + gasUsed2,
208207
'logs_bloom': '0x'
209208
};
209+
export const defaultContract = {
210+
"admin_key": null,
211+
"auto_renew_account": null,
212+
"auto_renew_period": 7776000,
213+
"contract_id": "0.0.1052",
214+
"created_timestamp": "1659622477.294172233",
215+
"deleted": false,
216+
"evm_address": null,
217+
"expiration_timestamp": null,
218+
"file_id": "0.0.1051",
219+
"max_automatic_token_associations": 0,
220+
"memo": "",
221+
"obtainer_id": null,
222+
"permanent_removal": null,
223+
"proxy_account_id": null,
224+
"timestamp": {
225+
"from": "1659622477.294172233",
226+
"to": null
227+
},
228+
"bytecode": "0x123456",
229+
"runtime_bytecode": "0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100321234"
230+
};
210231
export const defaultContractResults = {
211232
'results': [
212233
{

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

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { Registry } from 'prom-client';
2727
import sinon from 'sinon';
2828
import cache from 'js-cache';
2929
dotenv.config({ path: path.resolve(__dirname, '../test.env') });
30-
import {RelayImpl, MirrorNodeClientError} from '@hashgraph/json-rpc-relay';
30+
import { RelayImpl } from '@hashgraph/json-rpc-relay';
3131
import { predefined } from '../../src/lib/errors/JsonRpcError';
3232
import { EthImpl } from '../../src/lib/eth';
3333
import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient';
@@ -417,7 +417,7 @@ describe('Eth calls using MirrorNode', async function () {
417417
"contract_id": "0.0.1052",
418418
"created_timestamp": "1659622477.294172233",
419419
"deleted": false,
420-
"evm_address": contractAddress1,
420+
"evm_address": null,
421421
"expiration_timestamp": null,
422422
"file_id": "0.0.1051",
423423
"max_automatic_token_associations": 0,
@@ -805,7 +805,7 @@ describe('Eth calls using MirrorNode', async function () {
805805
try {
806806
await ethImpl.getBlockByHash(blockHash, false);
807807
} catch (e) {
808-
console.log(e);
808+
809809
expect(e.code).to.equal(-32603);
810810
expect(e.name).to.equal('Internal error');
811811
}
@@ -931,7 +931,6 @@ describe('Eth calls using MirrorNode', async function () {
931931
try {
932932
await ethImpl.getTransactionByBlockNumberAndIndex(EthImpl.numberTo0x(defaultBlock.number), EthImpl.numberTo0x(defaultBlock.count));
933933
} catch (e) {
934-
console.log(e);
935934
expect(e.code).to.equal(-32603);
936935
expect(e.name).to.equal('Internal error');
937936
}
@@ -1036,7 +1035,6 @@ describe('Eth calls using MirrorNode', async function () {
10361035
try {
10371036
await ethImpl.getTransactionByBlockHashAndIndex(defaultBlock.hash, EthImpl.numberTo0x(defaultBlock.count));
10381037
} catch (e) {
1039-
console.log(e);
10401038
expect(e.code).to.equal(-32603);
10411039
expect(e.name).to.equal('Internal error');
10421040
}
@@ -1454,13 +1452,58 @@ describe('Eth calls using MirrorNode', async function () {
14541452
});
14551453

14561454
it('no filters', async function () {
1455+
const filteredLogs = {
1456+
logs: [
1457+
defaultLogs.logs[0],
1458+
{...defaultLogs.logs[1], address: "0x0000000000000000000000000000000002131952"},
1459+
{...defaultLogs.logs[2], address: "0x0000000000000000000000000000000002131953"},
1460+
{...defaultLogs.logs[3], address: "0x0000000000000000000000000000000002131954"}
1461+
]
1462+
};
14571463
mock.onGet("blocks?limit=1&order=desc").reply(200, { blocks: [defaultBlock] });
1458-
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, defaultLogs);
1464+
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, filteredLogs);
1465+
filteredLogs.logs.forEach((log, index) => {
1466+
mock.onGet(`contracts/${log.address}`).reply(200, {...defaultContract, contract_id: `0.0.105${index}`});
1467+
});
1468+
1469+
const result = await ethImpl.getLogs(null, null, null, null, null);
1470+
expect(result).to.exist;
1471+
1472+
expect(result.length).to.eq(4);
1473+
expectLogData(result[0], filteredLogs.logs[0], defaultDetailedContractResults);
1474+
expectLogData(result[1], filteredLogs.logs[1], defaultDetailedContractResults);
1475+
expectLogData(result[2], filteredLogs.logs[2], defaultDetailedContractResults2);
1476+
expectLogData(result[3], filteredLogs.logs[3], defaultDetailedContractResults3);
1477+
});
1478+
1479+
it('Should return evm address if contract has one', async function () {
1480+
const filteredLogs = {
1481+
logs: [defaultLogs.logs[0]]
1482+
};
1483+
1484+
mock.onGet("blocks?limit=1&order=desc").reply(200, { blocks: [defaultBlock] });
1485+
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, filteredLogs);
1486+
mock.onGet(`contracts/${filteredLogs.logs[0].address}`).reply(200, {...defaultContract, evm_address: defaultEvmAddress});
14591487

14601488
const result = await ethImpl.getLogs(null, null, null, null, null);
14611489
expect(result).to.exist;
14621490

1491+
expect(result.length).to.eq(1);
1492+
expect(result[0].address).to.eq(defaultEvmAddress);
1493+
});
1494+
1495+
it('Should cache contracts/contractIdOrAddress request', async function () {
1496+
mock.onGet("blocks?limit=1&order=desc").reply(200, { blocks: [defaultBlock] });
1497+
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, defaultLogs);
1498+
mock.onGet(`contracts/${defaultLogs.logs[0].address}`).replyOnce(200, defaultContract); // This mock will fire only once, if the request is not cached, the test will fail with no mock error
1499+
1500+
const result = await ethImpl.getLogs(null, null, null, null, null);
1501+
1502+
expect(cache.keys().includes('getLogEvmAddress.0x0000000000000000000000000000000002131951')).to.be.true;
1503+
1504+
expect(result).to.exist;
14631505
expect(result.length).to.eq(4);
1506+
14641507
expectLogData1(result[0]);
14651508
expectLogData2(result[1]);
14661509
expectLogData3(result[2]);
@@ -1473,6 +1516,9 @@ describe('Eth calls using MirrorNode', async function () {
14731516
};
14741517
mock.onGet("blocks?limit=1&order=desc").reply(200, { blocks: [defaultBlock] });
14751518
mock.onGet(`contracts/${contractAddress1}/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, filteredLogs);
1519+
for (const log of filteredLogs.logs) {
1520+
mock.onGet(`contracts/${log.address}`).reply(200, defaultContract);
1521+
}
14761522

14771523
const result = await ethImpl.getLogs(null, null, null, contractAddress1, null);
14781524

@@ -1491,6 +1537,9 @@ describe('Eth calls using MirrorNode', async function () {
14911537

14921538
mock.onGet(`blocks/${blockHash}`).reply(200, defaultBlock);
14931539
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, filteredLogs);
1540+
for (const log of filteredLogs.logs) {
1541+
mock.onGet(`contracts/${log.address}`).reply(200, defaultContract);
1542+
}
14941543

14951544
const result = await ethImpl.getLogs(blockHash, null, null, null, null);
14961545

@@ -1515,6 +1564,9 @@ describe('Eth calls using MirrorNode', async function () {
15151564
mock.onGet('blocks/5').reply(200, defaultBlock);
15161565
mock.onGet('blocks/16').reply(200, toBlock);
15171566
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${toBlock.timestamp.to}`).reply(200, filteredLogs);
1567+
for (const log of filteredLogs.logs) {
1568+
mock.onGet(`contracts/${log.address}`).reply(200, defaultContract);
1569+
}
15181570

15191571
const result = await ethImpl.getLogs(null, '0x5', '0x10', null, null);
15201572

@@ -1526,6 +1578,7 @@ describe('Eth calls using MirrorNode', async function () {
15261578
it('with non-existing fromBlock filter', async function () {
15271579
mock.onGet('blocks/5').reply(200, defaultBlock);
15281580
mock.onGet('blocks/16').reply(404, {"_status": { "messages": [{"message": "Not found"}]}});
1581+
15291582
const result = await ethImpl.getLogs(null, '0x10', '0x5', null, null);
15301583

15311584
expect(result).to.exist;
@@ -1540,6 +1593,8 @@ describe('Eth calls using MirrorNode', async function () {
15401593
mock.onGet('blocks/5').reply(200, defaultBlock);
15411594
mock.onGet('blocks/16').reply(404, {"_status": { "messages": [{"message": "Not found"}]}});
15421595
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}`).reply(200, filteredLogs);
1596+
mock.onGet(`contracts/${filteredLogs.logs[0].address}`).reply(200, defaultContract);
1597+
15431598
const result = await ethImpl.getLogs(null, '0x5', '0x10', null, null);
15441599

15451600
expect(result).to.exist;
@@ -1571,6 +1626,9 @@ describe('Eth calls using MirrorNode', async function () {
15711626

15721627
mock.onGet('blocks?limit=1&order=desc').reply(200, { blocks: [defaultBlock] });
15731628
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, filteredLogs);
1629+
for (const log of filteredLogs.logs) {
1630+
mock.onGet(`contracts/${log.address}`).reply(200, defaultContract);
1631+
}
15741632

15751633
const result = await ethImpl.getLogs(null, null, 'latest', null, null);
15761634

@@ -1610,6 +1668,9 @@ describe('Eth calls using MirrorNode', async function () {
16101668
`&topic0=${defaultLogTopics[0]}&topic1=${defaultLogTopics[1]}` +
16111669
`&topic2=${defaultLogTopics[2]}&topic3=${defaultLogTopics[3]}`
16121670
).reply(200, filteredLogs);
1671+
for (const log of filteredLogs.logs) {
1672+
mock.onGet(`contracts/${log.address}`).reply(200, defaultContract);
1673+
}
16131674

16141675
const result = await ethImpl.getLogs(null, null, null, null, defaultLogTopics);
16151676

@@ -1632,6 +1693,9 @@ describe('Eth calls using MirrorNode', async function () {
16321693
`&topic0=${defaultLogTopics[0]}&topic1=${defaultLogTopics[1]}` +
16331694
`&topic2=${defaultLogTopics[2]}&topic3=${defaultLogTopics[3]}`
16341695
).reply(200, filteredLogs);
1696+
for (const log of filteredLogs.logs) {
1697+
mock.onGet(`contracts/${log.address}`).reply(200, defaultContract);
1698+
}
16351699

16361700
const result = await ethImpl.getLogs(null, '0x5', '0x10', null, defaultLogTopics);
16371701

0 commit comments

Comments
 (0)