Skip to content

Commit 32ea15b

Browse files
Return correct error for rate limit (#1269)
* Return correct error for rate limit Signed-off-by: georgi-l95 <[email protected]> * added 400 and 429 to expected error codes on handleError Unit Test Signed-off-by: Alfredo Gutierrez <[email protected]> * fixed unit test for handleError when error code is 400 Signed-off-by: Alfredo Gutierrez <[email protected]> * fixed precompile acceptance tests Signed-off-by: Alfredo Gutierrez <[email protected]> * improved log message when accepting error for eth_call Signed-off-by: Alfredo Gutierrez <[email protected]> * fixed rpc_batch3 acceptance test Signed-off-by: Alfredo Gutierrez <[email protected]> * exposing error when 400 (CONTRACT_REVERT) is thrown from mirrorNodeClient request, and UT to validate is working. Signed-off-by: Alfredo Gutierrez <[email protected]> --------- Signed-off-by: georgi-l95 <[email protected]> Signed-off-by: Alfredo Gutierrez <[email protected]> Co-authored-by: Alfredo Gutierrez <[email protected]>
1 parent 080fd39 commit 32ea15b

File tree

8 files changed

+123
-29
lines changed

8 files changed

+123
-29
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class MirrorNodeClient {
9797
[MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT, [400, 404]],
9898
[MirrorNodeClient.GET_TOKENS_ENDPOINT, [400, 404]],
9999
[MirrorNodeClient.GET_TRANSACTIONS_ENDPOINT, [400, 404]],
100-
[MirrorNodeClient.CONTRACT_CALL_ENDPOINT, [400,404,415,429,500]],
100+
[MirrorNodeClient.CONTRACT_CALL_ENDPOINT, [404, 415, 500]],
101101
[MirrorNodeClient.GET_STATE_ENDPOINT, [400, 404]]
102102
]);
103103

@@ -305,7 +305,7 @@ export class MirrorNodeClient {
305305
if (error.response && acceptedErrorResponses && acceptedErrorResponses.indexOf(effectiveStatusCode) !== -1) {
306306
this.logger.debug(`${requestIdPrefix} [${method}] ${path} ${effectiveStatusCode} status`);
307307
if(pathLabel === MirrorNodeClient.CONTRACT_CALL_ENDPOINT) {
308-
this.logger.warn(`${requestIdPrefix} [${method}] ${path} Error details: (StatusText: '${error.response.statusText}' Data: '${JSON.stringify(error.response.data)}'"')`);
308+
this.logger.warn(`${requestIdPrefix} [${method}] ${path} Error details: ( StatusCode: '${effectiveStatusCode}', StatusText: '${error.response.statusText}', Data: '${JSON.stringify(error.response.data)}')`);
309309
}
310310
return null;
311311
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class MirrorNodeClientError extends Error {
3131

3232
static statusCodes = {
3333
NOT_FOUND: 404,
34+
TOO_MANY_REQUESTS: 429,
3435
NO_CONTENT: 204
3536
};
3637

@@ -71,6 +72,10 @@ export class MirrorNodeClientError extends Error {
7172
return this.statusCode === MirrorNodeClientError.statusCodes.NO_CONTENT;
7273
}
7374

75+
public isRateLimit(): boolean {
76+
return this.statusCode === MirrorNodeClientError.statusCodes.TOO_MANY_REQUESTS;
77+
}
78+
7479
public isNotSupportedSystemContractOperaton(): boolean {
7580
return this.message === 'Precompile not supported';
7681
}

packages/relay/src/lib/eth.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,18 +1168,30 @@ export class EthImpl implements Eth {
11681168
const contractCallResponse = await this.mirrorNodeClient.postContractCall(callData, requestId);
11691169
return contractCallResponse && contractCallResponse.result ? EthImpl.prepend0x(contractCallResponse.result) : EthImpl.emptyHex;
11701170
} catch (e: any) {
1171-
// Temporary workaround until mirror node web3 module implements the support of precompiles
1172-
// If mirror node throws, rerun eth_call and force it to go through the Consensus network
1173-
if (e) {
1174-
const errorTypeMessage = e instanceof MirrorNodeClientError && (e.isNotSupported() || e.isNotSupportedSystemContractOperaton()) ? 'Unsupported' : 'Unhandled';
1175-
this.logger.trace(`${requestIdPrefix} ${errorTypeMessage} mirror node eth_call request, retrying with consensus node`);
1176-
1177-
return await this.callConsensusNode(call, gas, requestId);
1178-
}
1179-
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit eth_call`);
11801171
if (e instanceof JsonRpcError) {
11811172
return e;
11821173
}
1174+
1175+
if (e instanceof MirrorNodeClientError) {
1176+
if (e.isRateLimit()) {
1177+
return predefined.IP_RATE_LIMIT_EXCEEDED(e.errorMessage || `Rate limit exceeded on ${EthImpl.ethCall}`);
1178+
}
1179+
1180+
if (e.isContractReverted()) {
1181+
return predefined.CONTRACT_REVERT(e.errorMessage);
1182+
}
1183+
1184+
// Temporary workaround until mirror node web3 module implements the support of precompiles
1185+
// If mirror node throws, rerun eth_call and force it to go through the Consensus network
1186+
if (e.isNotSupported() || e.isNotSupportedSystemContractOperaton()) {
1187+
const errorTypeMessage = e.isNotSupported() || e.isNotSupportedSystemContractOperaton() ? 'Unsupported' : 'Unhandled';
1188+
this.logger.trace(`${requestIdPrefix} ${errorTypeMessage} mirror node eth_call request, retrying with consensus node`);
1189+
return await this.callConsensusNode(call, gas, requestId);
1190+
}
1191+
}
1192+
1193+
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit eth_call`);
1194+
11831195
return predefined.INTERNAL_ERROR(e.message.toString());
11841196
}
11851197
}

packages/relay/tests/helpers.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,28 @@ const mockData = {
166166
}
167167
]
168168
}
169+
},
170+
171+
tooManyRequests: {
172+
"_status": {
173+
"messages": [
174+
{
175+
"message": "Too Many Requests"
176+
}
177+
]
178+
}
179+
},
180+
181+
contractReverted: {
182+
"_status": {
183+
"messages": [
184+
{
185+
"message": "CONTRACT_REVERT_EXECUTED",
186+
"detail": "",
187+
"data": "0x"
188+
}
189+
]
190+
}
169191
}
170192
};
171193

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import {
3535
defaultFromLongZeroAddress,
3636
expectUnsupportedMethod,
3737
defaultErrorMessage,
38-
buildCryptoTransferTransaction
38+
buildCryptoTransferTransaction,
39+
mockData
3940
} from '../helpers';
4041

4142
import pino from 'pino';
@@ -3259,7 +3260,9 @@ describe('Eth calls using MirrorNode', async function () {
32593260

32603261
const result = await ethImpl.call(callData, 'latest');
32613262
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, 15_000_000, accountAddress1, 'eth_call');
3262-
expect(result).to.equal("0x00");
3263+
expect(result).to.not.be.null;
3264+
expect(result.code).to.equal(-32603);
3265+
expect(result.name).to.equal("Internal error");
32633266
});
32643267

32653268
it('eth_call with no gas', async function () {
@@ -3313,6 +3316,36 @@ describe('Eth calls using MirrorNode', async function () {
33133316
expect(result).to.equal("0x00");
33143317
});
33153318

3319+
it('eth_call with all fields but mirrorNode throws 429', async function () {
3320+
const callData = {
3321+
...defaultCallData,
3322+
"from": accountAddress1,
3323+
"to": contractAddress2,
3324+
"data": contractCallData,
3325+
"gas": maxGasLimit
3326+
};
3327+
web3Mock.onPost('contracts/call', {...callData, estimate: false}).reply(429, mockData.tooManyRequests);
3328+
const result = await ethImpl.call(callData, 'latest');
3329+
expect(result).to.be.not.null;
3330+
expect(result.code).to.eq(-32605);
3331+
expect(result.name).to.eq("IP Rate limit exceeded");
3332+
});
3333+
3334+
it('eth_call with all fields but mirrorNode throws 400', async function () {
3335+
const callData = {
3336+
...defaultCallData,
3337+
"from": accountAddress1,
3338+
"to": contractAddress2,
3339+
"data": contractCallData,
3340+
"gas": maxGasLimit
3341+
};
3342+
web3Mock.onPost('contracts/call', {...callData, estimate: false}).reply(400, mockData.contractReverted);
3343+
const result = await ethImpl.call(callData, 'latest');
3344+
expect(result).to.be.not.null;
3345+
expect(result.code).to.eq(-32008);
3346+
expect(result.name).to.eq("Contract revert executed");
3347+
});
3348+
33163349
it('eth_call with all fields but mirrorNode throws 504 (timeout) on pre-check', async function () {
33173350

33183351
const timeoutAddress = "0x00000000000000000000000000000000000004e2";
@@ -3392,7 +3425,9 @@ describe('Eth calls using MirrorNode', async function () {
33923425
sinon.reset();
33933426
const result = await ethImpl.call(callData, 'latest');
33943427
sinon.assert.notCalled(sdkClientStub.submitContractCallQueryWithRetry);
3395-
expect(result).to.equal("0x");
3428+
expect(result).to.not.be.null;
3429+
expect(result.code).to.eq(-32008);
3430+
expect(result.name).to.eq('Contract revert executed');
33963431
});
33973432

33983433
it('caps gas at 15_000_000', async function () {

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ describe('MirrorNodeClient', async function () {
6060
describe('handleError', async() => {
6161

6262
const CONTRACT_CALL_ENDPOINT = 'contracts/call';
63-
const nullResponseCodes = [400,404,415,429,500];
64-
const errorRepsonseCodes = [501, 503];
63+
const nullResponseCodes = [404,415,500];
64+
const errorRepsonseCodes = [501, 503, 400, 429];
6565

6666
for (const code of nullResponseCodes) {
6767
it(`returns null when ${code} is returned`, async () => {
@@ -81,7 +81,11 @@ describe('MirrorNodeClient', async function () {
8181
await mirrorNodeInstance.handleError(error, CONTRACT_CALL_ENDPOINT, CONTRACT_CALL_ENDPOINT, code, 'POST');
8282
expect.fail('should have thrown an error');
8383
} catch (e) {
84-
expect(e.message).to.equal('test error');
84+
if(code === 400) {
85+
expect(e.message).to.equal('execution reverted: ');
86+
} else {
87+
expect(e.message).to.equal('test error');
88+
}
8589
}
8690
});
8791
}

packages/server/tests/acceptance/htsPrecompile/precompileCalls.spec.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -738,8 +738,12 @@ describe('@precompile-calls Tests for eth_call with HTS', async function () {
738738
data: CALLDATA_ALLOWANCE + NON_EXISTING_ACCOUNT.padStart(64, '0') + account2LongZero.replace('0x', '').padStart(64, '0')
739739
};
740740

741-
const res = await relay.call('eth_call', [callData, 'latest'], requestId);
742-
expect(res).to.eq('0x'); // confirm no error
741+
await relay.callFailing(
742+
'eth_call',
743+
[callData, 'latest'],
744+
predefined.CONTRACT_REVERT(),
745+
requestId
746+
);
743747
});
744748

745749

@@ -751,8 +755,12 @@ describe('@precompile-calls Tests for eth_call with HTS', async function () {
751755
data: CALLDATA_ALLOWANCE + adminAccountLongZero.replace('0x', '').padStart(64, '0') + NON_EXISTING_ACCOUNT.padStart(64, '0')
752756
};
753757

754-
const res = await relay.call('eth_call', [callData, 'latest'], requestId);
755-
expect(res).to.eq('0x'); // confirm no error
758+
await relay.callFailing(
759+
'eth_call',
760+
[callData, 'latest'],
761+
predefined.CONTRACT_REVERT(),
762+
requestId
763+
);
756764
});
757765
});
758766
});

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,7 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
348348
data: '0x3ec4de3800000000000000000000000067d8d32e9bf1a9968a5ff53b87d777aa8ebbee69'
349349
};
350350

351-
const res = await relay.call('eth_call', [callData, 'latest'], requestId);
352-
expect(res).to.eq('0x'); // confirm no error
351+
await relay.callFailing('eth_call', [callData, 'latest'], predefined.CONTRACT_REVERT(), requestId);
353352
});
354353

355354
it("007 'data' from request body with wrong encoded parameter", async function () {
@@ -424,8 +423,11 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
424423
data: PURE_METHOD_CALL_DATA
425424
};
426425

427-
const res = await relay.call('eth_call', [callData, 'latest'], requestId);
428-
expect(res).to.eq('0x'); // confirm no error
426+
await relay.callFailing('eth_call', [callData, 'latest'], {
427+
code: -32008,
428+
message: PURE_METHOD_ERROR_MESSAGE,
429+
data: PURE_METHOD_ERROR_DATA
430+
}, requestId);
429431
});
430432

431433
it('Returns revert message for view methods', async () => {
@@ -436,8 +438,11 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
436438
data: VIEW_METHOD_CALL_DATA
437439
};
438440

439-
const res = await relay.call('eth_call', [callData, 'latest'], requestId);
440-
expect(res).to.eq('0x'); // confirm no error
441+
await relay.callFailing('eth_call', [callData, 'latest'], {
442+
code: -32008,
443+
message: VIEW_METHOD_ERROR_MESSAGE,
444+
data: VIEW_METHOD_ERROR_DATA
445+
}, requestId);
441446
});
442447

443448
it('Returns revert reason in receipt for payable methods', async () => {
@@ -585,8 +590,11 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
585590
data: pureMethodsData[i].data
586591
};
587592

588-
const res = await relay.call('eth_call', [callData, 'latest'], requestId);
589-
expect(res).to.eq('0x'); // confirm no error
593+
await relay.callFailing('eth_call', [callData, 'latest'], {
594+
code: -32008,
595+
message: pureMethodsData[i].message,
596+
data: pureMethodsData[i].errorData
597+
}, requestId);
590598
});
591599
}
592600
});

0 commit comments

Comments
 (0)