Skip to content

Commit a376949

Browse files
Cherry-Pick: Add eth_call workaround for mirror-node empty response (#1132) (#1133)
Add `eth_call` workaround for mirror-node empty response (#1132) * add workaround and test * throw mirror node client error * check runtime_bytecode --------- Signed-off-by: georgi-l95 <[email protected]> Signed-off-by: Alfredo Gutierrez <[email protected]> Co-authored-by: Georgi Lazarov <[email protected]>
1 parent 12ddbb3 commit a376949

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

packages/relay/src/lib/errors/MirrorNodeClientError.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export class MirrorNodeClientError extends Error {
3232
};
3333

3434
static statusCodes = {
35-
NOT_FOUND: 404
35+
NOT_FOUND: 404,
36+
NO_CONTENT: 204
3637
};
3738

3839
constructor(error: any, statusCode: number) {
@@ -68,6 +69,10 @@ export class MirrorNodeClientError extends Error {
6869
return this.statusCode === MirrorNodeClientError.ErrorCodes.NOT_SUPPORTED;
6970
}
7071

72+
public isEmpty(): boolean {
73+
return this.statusCode === MirrorNodeClientError.statusCodes.NO_CONTENT;
74+
}
75+
7176
public isNotSupportedSystemContractOperaton(): boolean {
7277
return this.message === 'Precompile not supported';
7378
}

packages/relay/src/lib/eth.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,7 @@ export class EthImpl implements Eth {
10211021
const requestIdPrefix = formatRequestIdMessage(requestId);
10221022
this.logger.trace(`${requestIdPrefix} call(hash=${JSON.stringify(call)}, blockParam=${blockParam})`, call, blockParam);
10231023

1024-
await this.performCallChecks(call, requestId);
1024+
const to = await this.performCallChecks(call, requestId);
10251025

10261026
// Get a reasonable value for "gas" if it is not specified.
10271027
let gas = this.getCappedBlockGasLimit(call.gas);
@@ -1032,7 +1032,7 @@ export class EthImpl implements Eth {
10321032
if (process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE == 'false') {
10331033
//temporary workaround until precompiles are implemented in Mirror node evm module
10341034
// Execute the call and get the response
1035-
return await this.callMirrorNode(call, gas, value, requestId);
1035+
return await this.callMirrorNode(call, to, gas, value, requestId);
10361036
}
10371037

10381038
return await this.callConsensusNode(call, gas, requestId);
@@ -1042,9 +1042,14 @@ export class EthImpl implements Eth {
10421042
}
10431043
}
10441044

1045-
async callMirrorNode(call: any, gas: number, value: string | null, requestId?: string): Promise<string | JsonRpcError> {
1045+
async callMirrorNode(call: any, to: any, gas: number, value: string | null, requestId?: string): Promise<string | JsonRpcError> {
10461046
const requestIdPrefix = formatRequestIdMessage(requestId);
10471047
try {
1048+
if (to?.type === constants.TYPE_CONTRACT && to?.entity.runtime_bytecode === EthImpl.emptyHex) {
1049+
this.logger.trace(`${requestIdPrefix} Contract runtime_bytecode equals to 0x and mirror-node will return 0x as well, retrying with consensus node`);
1050+
throw new MirrorNodeClientError({ message: "Empty Response" }, MirrorNodeClientError.statusCodes.NO_CONTENT);
1051+
}
1052+
10481053
this.logger.debug(`${requestIdPrefix} Making eth_call on contract ${call.to} with gas ${gas} and call data "${call.data}" from "${call.from}" using mirror-node.`, call.to, gas, call.data, call.from);
10491054
const callData = {
10501055
...call,
@@ -1133,6 +1138,8 @@ export class EthImpl implements Eth {
11331138
if(!(toEntityType?.type === constants.TYPE_CONTRACT || toEntityType?.type === constants.TYPE_TOKEN)) {
11341139
throw predefined.NON_EXISTING_CONTRACT(call.to);
11351140
}
1141+
1142+
return toEntityType;
11361143
}
11371144

11381145
/**

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,30 @@ describe('Eth calls using MirrorNode', async function () {
542542
"contract_id": contractId2,
543543
};
544544

545+
const defaultContract3EmptyBytecode = {
546+
"address": contractAddress2,
547+
"contract_id": contractId2,
548+
"admin_key": null,
549+
"auto_renew_account": null,
550+
"auto_renew_period": 7776000,
551+
"created_timestamp": "1659622477.294172233",
552+
"deleted": false,
553+
"evm_address": null,
554+
"expiration_timestamp": null,
555+
"file_id": "0.0.1051",
556+
"max_automatic_token_associations": 0,
557+
"memo": "",
558+
"obtainer_id": null,
559+
"permanent_removal": null,
560+
"proxy_account_id": null,
561+
"timestamp": {
562+
"from": "1659622477.294172233",
563+
"to": null
564+
},
565+
"bytecode": "0x123456",
566+
"runtime_bytecode": "0x"
567+
};
568+
545569
const defaultHTSToken =
546570
{
547571
"admin_key": null,
@@ -2820,6 +2844,28 @@ describe('Eth calls using MirrorNode', async function () {
28202844
restMock.onGet(`tokens/${defaultContractResults.results[1].contract_id}`).reply(404, null);
28212845
});
28222846

2847+
it('eth_call with all fields, but mirror-node returns empty response', async function () {
2848+
const callData = {
2849+
...defaultCallData,
2850+
"from": accountAddress1,
2851+
"to": contractAddress2,
2852+
"data": contractCallData,
2853+
"gas": maxGasLimit
2854+
};
2855+
restMock.onGet(`contracts/${contractAddress2}`).reply(200, defaultContract3EmptyBytecode);
2856+
2857+
sdkClientStub.submitContractCallQueryWithRetry.returns({
2858+
asBytes: function () {
2859+
return Uint8Array.of(0);
2860+
}
2861+
}
2862+
);
2863+
2864+
const result = await ethImpl.call(callData, 'latest');
2865+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, 15_000_000, accountAddress1, 'eth_call');
2866+
expect(result).to.equal("0x00");
2867+
});
2868+
28232869
it('eth_call with no gas', async function () {
28242870
const callData = {
28252871
...defaultCallData,

0 commit comments

Comments
 (0)