Skip to content

Commit b63ce3b

Browse files
Cherrypick PR-1105 into 0.22 release (#1108)
Add Contract Call Query Retry Logic (#1105) * add contract call query retry logic * fix tests * remove unused --------- Signed-off-by: georgi-l95 <[email protected]> Signed-off-by: Alfredo Gutierrez <[email protected]> Co-authored-by: Georgi Lazarov <[email protected]>
1 parent e4728fb commit b63ce3b

File tree

8 files changed

+73
-43
lines changed

8 files changed

+73
-43
lines changed

docs/configuration.md

Lines changed: 27 additions & 28 deletions
Large diffs are not rendered by default.

helm-chart/templates/configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ data:
3636
CONSENSUS_MAX_EXECUTION_TIME: {{ .Values.config.CONSENSUS_MAX_EXECUTION_TIME | quote }}
3737
SUBSCRIPTIONS_ENABLED: {{ .Values.config.SUBSCRIPTIONS_ENABLED | quote }}
3838
ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: {{ .Values.config.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE | quote }}
39+
CONTRACT_QUERY_TIMEOUT_RETRIES: {{ .Values.config.CONTRACT_QUERY_TIMEOUT_RETRIES | quote }}

helm-chart/value-test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ config:
123123
ETH_CALL_CACHE_TTL: 200
124124
CONSENSUS_MAX_EXECUTION_TIME: 15000
125125
SDK_REQUEST_TIMEOUT: 10000
126+
CONTRACT_QUERY_TIMEOUT_RETRIES: 3
126127
SUBSCRIPTIONS_ENABLED: false
127128
ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: false
128129

helm-chart/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ config:
124124
ETH_CALL_CACHE_TTL: 200
125125
CONSENSUS_MAX_EXECUTION_TIME: 15000
126126
SDK_REQUEST_TIMEOUT: 10000
127+
CONTRACT_QUERY_TIMEOUT_RETRIES: 3
127128
SUBSCRIPTIONS_ENABLED: false
128129
ETH_CALL_DEFAULT_TO_CONSENSUS_NODE: true
129130

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,30 @@ export class SDKClient {
277277
return this.executeQuery(contractCallQuery, this.clientMain, callerName, to, requestId);
278278
}
279279

280+
async submitContractCallQueryWithRetry(to: string, data: string, gas: number, from: string, callerName: string, requestId?: string): Promise<ContractFunctionResult> {
281+
const requestIdPrefix = formatRequestIdMessage(requestId);
282+
let retries = 0;
283+
let resp;
284+
while (parseInt(process.env.CONTRACT_QUERY_TIMEOUT_RETRIES || '1') > retries) {
285+
console.log(retries)
286+
try {
287+
resp = await this.submitContractCallQuery(to, data, gas, from, callerName, requestId);
288+
return resp;
289+
} catch (e: any) {
290+
const sdkClientError = new SDKClientError(e, e.message);
291+
if (sdkClientError.isTimeoutExceeded()) {
292+
const delay = retries * 1000;
293+
this.logger.trace(`${requestIdPrefix} Contract call query failed with status ${sdkClientError.message}. Retrying again after ${delay}ms ...`);
294+
retries++;
295+
await new Promise(r => setTimeout(r, delay));
296+
continue;
297+
}
298+
throw sdkClientError;
299+
}
300+
}
301+
return resp;
302+
}
303+
280304
async increaseCostAndRetryExecution(query: Query<any>, baseCost: Hbar, client: Client, maxRetries: number, currentRetry: number, requestId?: string) {
281305
const baseMultiplier = constants.QUERY_COST_INCREMENTATION_STEP;
282306
const multiplier = Math.pow(baseMultiplier, currentRetry);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export class SDKClientError extends Error {
6060
return this.statusCode == Status.ContractRevertExecuted._code;
6161
}
6262

63+
public isTimeoutExceeded(): boolean {
64+
return this.statusCode === Status.Unknown._code && this.message?.includes("timeout exceeded");
65+
}
66+
6367
public isGrpcTimeout(): boolean {
6468
// The SDK uses the same code for Grpc Timeout as INVALID_TRANSACTION_ID
6569
return this.statusCode === Status.InvalidTransactionId._code;

packages/relay/src/lib/eth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ export class EthImpl implements Eth {
10991099
return cachedResponse;
11001100
}
11011101

1102-
const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId);
1102+
const contractCallResponse = await this.sdkClient.submitContractCallQueryWithRetry(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId);
11031103
const formattedCallReponse = EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
11041104

11051105
this.cache.set(cacheKey, formattedCallReponse, { ttl: EthImpl.ethCallCacheTtl });

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2627,7 +2627,7 @@ describe('Eth calls using MirrorNode', async function () {
26272627
});
26282628
restMock.onGet(`contracts/${contractAddress2}`).reply(200, defaultContract2);
26292629

2630-
sdkClientStub.submitContractCallQuery.returns({
2630+
sdkClientStub.submitContractCallQueryWithRetry.returns({
26312631
asBytes: function () {
26322632
return Uint8Array.of(0);
26332633
}
@@ -2640,12 +2640,12 @@ describe('Eth calls using MirrorNode', async function () {
26402640
"data": contractCallData,
26412641
}, 'latest');
26422642

2643-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, 400_000, accountAddress1, 'eth_call');
2643+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, 400_000, accountAddress1, 'eth_call');
26442644
expect(result).to.equal("0x00");
26452645
});
26462646

26472647
it('eth_call with no data', async function () {
2648-
sdkClientStub.submitContractCallQuery.returns({
2648+
sdkClientStub.submitContractCallQueryWithRetry.returns({
26492649
asBytes: function () {
26502650
return Uint8Array.of(0);
26512651
}
@@ -2658,12 +2658,12 @@ describe('Eth calls using MirrorNode', async function () {
26582658
"gas": maxGasLimitHex
26592659
}, 'latest');
26602660

2661-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, undefined, maxGasLimit, accountAddress1, 'eth_call');
2661+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, undefined, maxGasLimit, accountAddress1, 'eth_call');
26622662
expect(result).to.equal("0x00");
26632663
});
26642664

26652665
it('eth_call with no from address', async function () {
2666-
sdkClientStub.submitContractCallQuery.returns({
2666+
sdkClientStub.submitContractCallQueryWithRetry.returns({
26672667
asBytes: function () {
26682668
return Uint8Array.of(0);
26692669
}
@@ -2676,12 +2676,12 @@ describe('Eth calls using MirrorNode', async function () {
26762676
"gas": maxGasLimitHex
26772677
}, 'latest');
26782678

2679-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, undefined, 'eth_call');
2679+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, undefined, 'eth_call');
26802680
expect(result).to.equal("0x00");
26812681
});
26822682

26832683
it('eth_call with all fields', async function () {
2684-
sdkClientStub.submitContractCallQuery.returns({
2684+
sdkClientStub.submitContractCallQueryWithRetry.returns({
26852685
asBytes: function () {
26862686
return Uint8Array.of(0);
26872687
}
@@ -2695,13 +2695,13 @@ describe('Eth calls using MirrorNode', async function () {
26952695
"gas": maxGasLimitHex
26962696
}, 'latest');
26972697

2698-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
2698+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
26992699
expect(result).to.equal("0x00");
27002700
});
27012701

27022702
//Return once the value, then it's being fetched from cache. After the loop we reset the sdkClientStub, so that it returns nothing, if we get an error in the next request that means that the cache was cleared.
27032703
it('eth_call should cache the response for 200ms', async function () {
2704-
sdkClientStub.submitContractCallQuery.returns({
2704+
sdkClientStub.submitContractCallQueryWithRetry.returns({
27052705
asBytes: function () {
27062706
return Uint8Array.of(0);
27072707
}
@@ -2737,7 +2737,7 @@ describe('Eth calls using MirrorNode', async function () {
27372737

27382738
describe('with gas > 15_000_000', async function() {
27392739
it('caps gas at 15_000_000', async function () {
2740-
sdkClientStub.submitContractCallQuery.returns({
2740+
sdkClientStub.submitContractCallQueryWithRetry.returns({
27412741
asBytes: function () {
27422742
return Uint8Array.of(0);
27432743
}
@@ -2751,13 +2751,13 @@ describe('Eth calls using MirrorNode', async function () {
27512751
"gas": 50_000_000
27522752
}, 'latest');
27532753

2754-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, 15_000_000, accountAddress1, 'eth_call');
2754+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, 15_000_000, accountAddress1, 'eth_call');
27552755
expect(result).to.equal("0x00");
27562756
});
27572757
});
27582758

27592759
it('SDK returns a precheck error', async function () {
2760-
sdkClientStub.submitContractCallQuery.throws(predefined.CONTRACT_REVERT(defaultErrorMessage));
2760+
sdkClientStub.submitContractCallQueryWithRetry.throws(predefined.CONTRACT_REVERT(defaultErrorMessage));
27612761

27622762
const result = await ethImpl.call({
27632763
"from": accountAddress1,
@@ -2890,7 +2890,7 @@ describe('Eth calls using MirrorNode', async function () {
28902890
}
28912891
});
28922892

2893-
sdkClientStub.submitContractCallQuery.returns({
2893+
sdkClientStub.submitContractCallQueryWithRetry.returns({
28942894
asBytes: function () {
28952895
return Uint8Array.of(0);
28962896
}
@@ -2899,7 +2899,7 @@ describe('Eth calls using MirrorNode', async function () {
28992899

29002900
const result = await ethImpl.call(callData, 'latest');
29012901

2902-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
2902+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
29032903
expect(result).to.equal("0x00");
29042904
});
29052905

0 commit comments

Comments
 (0)