Skip to content

Commit 4ec32ae

Browse files
Nana-ECgeorgi-l95
andauthored
Cherry-pick: Expand eth_call mirror node consensus retry scenarios (#1122) (#1125)
Expand eth_call mirror node consensus retry scenarios (#1122) Expand eth_call mirror node consensus retry scenarios Move consensusNode call in catch out to apply to all error cases Split mirror node call case into separate method with catch including consensus call Add toNullableBigNumber to manage value conversion Add getCappedBlockGasLimit to manage block gas limit max --------- Signed-off-by: Nana Essilfie-Conduah <[email protected]> Signed-off-by: georgi-l95 <[email protected]> Co-authored-by: georgi-l95 <[email protected]>
1 parent 827c17c commit 4ec32ae

File tree

4 files changed

+91
-39
lines changed

4 files changed

+91
-39
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ export class SDKClient {
282282
let retries = 0;
283283
let resp;
284284
while (parseInt(process.env.CONTRACT_QUERY_TIMEOUT_RETRIES || '1') > retries) {
285-
console.log(retries)
286285
try {
287286
resp = await this.submitContractCallQuery(to, data, gas, from, callerName, requestId);
288287
return resp;
@@ -295,6 +294,9 @@ export class SDKClient {
295294
await new Promise(r => setTimeout(r, delay));
296295
continue;
297296
}
297+
if (e instanceof JsonRpcError) {
298+
throw e;
299+
}
298300
throw sdkClientError;
299301
}
300302
}
@@ -397,7 +399,6 @@ export class SDKClient {
397399
if (sdkClientError.isGrpcTimeout()) {
398400
throw predefined.REQUEST_TIMEOUT;
399401
}
400-
401402
throw sdkClientError;
402403
}
403404
};

packages/relay/src/lib/eth.ts

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,52 +1024,48 @@ export class EthImpl implements Eth {
10241024
await this.performCallChecks(call, requestId);
10251025

10261026
// Get a reasonable value for "gas" if it is not specified.
1027-
let gas = Number(call.gas) || 400_000;
1028-
1029-
let value: string | null = null;
1030-
if (typeof call.value === 'string') {
1031-
value = (new BN(call.value)).toString();
1032-
}
1033-
1034-
// Gas limit for `eth_call` is 50_000_000, but the current Hedera network limit is 15_000_000
1035-
// With values over the gas limit, the call will fail with BUSY error so we cap it at 15_000_000
1036-
if (gas > constants.BLOCK_GAS_LIMIT) {
1037-
this.logger.trace(`${requestIdPrefix} eth_call gas amount (${gas}) exceeds network limit, capping gas to ${constants.BLOCK_GAS_LIMIT}`);
1038-
gas = constants.BLOCK_GAS_LIMIT;
1039-
}
1027+
let gas = this.getCappedBlockGasLimit(call.gas);
1028+
let value: string | null = EthImpl.toNullableBigNumber(call.value);
10401029

10411030
try {
10421031
// ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = false enables the use of Mirror node
10431032
if (process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE == 'false') {
10441033
//temporary workaround until precompiles are implemented in Mirror node evm module
10451034
// Execute the call and get the response
1046-
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);
1047-
const callData = {
1048-
...call,
1049-
gas,
1050-
value,
1051-
estimate: false
1052-
}
1053-
const contractCallResponse = await this.mirrorNodeClient.postContractCall(callData, requestId);
1054-
if (contractCallResponse && contractCallResponse.result) {
1055-
return EthImpl.prepend0x(contractCallResponse.result);
1056-
}
1057-
return EthImpl.emptyHex;
1035+
return await this.callMirrorNode(call, gas, value, requestId);
10581036
}
10591037

10601038
return await this.callConsensusNode(call, gas, requestId);
10611039
} catch (e: any) {
1062-
// Temporary workaround until mirror node web3 module implements the support of precompiles
1063-
// If mirror node throws NOT_SUPPORTED or precompile is not supported, rerun eth_call and force it to go through the Consensus network
1064-
if (e instanceof MirrorNodeClientError && (e.isNotSupported() || e.isNotSupportedSystemContractOperaton())) {
1065-
return await this.callConsensusNode(call, gas, requestId);
1066-
}
1040+
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit eth_call`);
1041+
return e instanceof JsonRpcError ? e : predefined.INTERNAL_ERROR();
1042+
}
1043+
}
10671044

1068-
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit contractCallQuery`);
1069-
if (e instanceof JsonRpcError) {
1070-
return e;
1045+
async callMirrorNode(call: any, gas: number, value: string | null, requestId?: string): Promise<string | JsonRpcError> {
1046+
const requestIdPrefix = formatRequestIdMessage(requestId);
1047+
try {
1048+
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);
1049+
const callData = {
1050+
...call,
1051+
gas,
1052+
value,
1053+
estimate: false
10711054
}
1072-
return predefined.INTERNAL_ERROR();
1055+
1056+
const contractCallResponse = await this.mirrorNodeClient.postContractCall(callData, requestId);
1057+
return contractCallResponse && contractCallResponse.result ? EthImpl.prepend0x(contractCallResponse.result) : EthImpl.emptyHex;
1058+
} catch (e: any) {
1059+
// Temporary workaround until mirror node web3 module implements the support of precompiles
1060+
// If mirror node throws, rerun eth_call and force it to go through the Consensus network
1061+
if (e) {
1062+
const errorTypeMessage = e instanceof MirrorNodeClientError && (e.isNotSupported() || e.isNotSupportedSystemContractOperaton()) ? 'Unsupported' : 'Unhandled';
1063+
this.logger.trace(`${requestIdPrefix} ${errorTypeMessage} mirror node eth_call request, retrying with consensus node`);
1064+
1065+
return await this.callConsensusNode(call, gas, requestId);
1066+
}
1067+
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit eth_call`);
1068+
return e instanceof JsonRpcError ? e : predefined.INTERNAL_ERROR();
10731069
}
10741070
}
10751071

@@ -1292,6 +1288,14 @@ export class EthImpl implements Eth {
12921288
return value.substring(0, 66);
12931289
}
12941290

1291+
static toNullableBigNumber(value: string): string | null {
1292+
if (typeof value === 'string') {
1293+
return (new BN(value)).toString();
1294+
}
1295+
1296+
return null;
1297+
}
1298+
12951299
private static toNullIfEmptyHex(value: string): string | null {
12961300
return value === EthImpl.emptyHex ? null : value;
12971301
}
@@ -1330,6 +1334,22 @@ export class EthImpl implements Eth {
13301334
}
13311335
}
13321336

1337+
private getCappedBlockGasLimit(gasString: string, requestIdPrefix?: string): number {
1338+
if (!gasString) {
1339+
return 400_000;
1340+
}
1341+
1342+
// Gas limit for `eth_call` is 50_000_000, but the current Hedera network limit is 15_000_000
1343+
// With values over the gas limit, the call will fail with BUSY error so we cap it at 15_000_000
1344+
let gas = Number.parseInt(gasString);
1345+
if (gas > constants.BLOCK_GAS_LIMIT) {
1346+
this.logger.trace(`${requestIdPrefix} eth_call gas amount (${gas}) exceeds network limit, capping gas to ${constants.BLOCK_GAS_LIMIT}`);
1347+
return constants.BLOCK_GAS_LIMIT;
1348+
}
1349+
1350+
return gas;
1351+
}
1352+
13331353
/**
13341354
* Gets the block with the given hash.
13351355
* Given an ethereum transaction hash, call the mirror node to get the block info.

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2903,6 +2903,38 @@ describe('Eth calls using MirrorNode', async function () {
29032903
expect(result).to.equal("0x00");
29042904
});
29052905

2906+
it('eth_call with all fields, but mirror node throws CONTRACT_REVERTED', async function () {
2907+
const callData = {
2908+
...defaultCallData,
2909+
"from": accountAddress1,
2910+
"to": contractAddress2,
2911+
"data": contractCallData,
2912+
"gas": maxGasLimit
2913+
};
2914+
2915+
web3Mock.onPost('contracts/call', {...callData, estimate: false}).reply(400, {
2916+
'_status': {
2917+
'messages': [
2918+
{
2919+
'message': 'Contract reverted execution'
2920+
}
2921+
]
2922+
}
2923+
});
2924+
2925+
sdkClientStub.submitContractCallQueryWithRetry.returns({
2926+
asBytes: function () {
2927+
return Uint8Array.of(0);
2928+
}
2929+
}
2930+
);
2931+
2932+
const result = await ethImpl.call(callData, 'latest');
2933+
2934+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
2935+
expect(result).to.equal("0x00");
2936+
});
2937+
29062938
it('caps gas at 15_000_000', async function () {
29072939
const callData = {
29082940
...defaultCallData,

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
393393
expect(res).to.eq('0x00000000000000000000000000000000000000000000000000000000000003e8');
394394
});
395395

396-
397-
it("011 Should fail when calling msgValue with more value than available balance", async function () {
396+
// test is pending until fallback workflow to consensus node is removed, because this flow works when calling to consensus
397+
xit("011 Should fail when calling msgValue with more value than available balance", async function () {
398398
const callData = {
399399
...defaultCallData,
400400
data: '0xddf363d7',
@@ -407,7 +407,6 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
407407
} catch (e) {
408408
Assertions.jsonRpcError(e, predefined.CONTRACT_REVERT());
409409
}
410-
411410
});
412411
}
413412
});

0 commit comments

Comments
 (0)