Skip to content

Commit 1eaa12b

Browse files
Cherrypick PR-1105 into 0.21 release (#1111)
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 f9e3b90 commit 1eaa12b

File tree

8 files changed

+73
-42
lines changed

8 files changed

+73
-42
lines changed

docs/configuration.md

Lines changed: 27 additions & 27 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
@@ -1052,7 +1052,7 @@ export class EthImpl implements Eth {
10521052
return cachedResponse;
10531053
}
10541054

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

10581058
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
@@ -2491,7 +2491,7 @@ describe('Eth calls using MirrorNode', async function () {
24912491
});
24922492
restMock.onGet(`contracts/${contractAddress2}`).reply(200, defaultContract2);
24932493

2494-
sdkClientStub.submitContractCallQuery.returns({
2494+
sdkClientStub.submitContractCallQueryWithRetry.returns({
24952495
asBytes: function () {
24962496
return Uint8Array.of(0);
24972497
}
@@ -2504,12 +2504,12 @@ describe('Eth calls using MirrorNode', async function () {
25042504
"data": contractCallData,
25052505
}, 'latest');
25062506

2507-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, 400_000, accountAddress1, 'eth_call');
2507+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, 400_000, accountAddress1, 'eth_call');
25082508
expect(result).to.equal("0x00");
25092509
});
25102510

25112511
it('eth_call with no data', async function () {
2512-
sdkClientStub.submitContractCallQuery.returns({
2512+
sdkClientStub.submitContractCallQueryWithRetry.returns({
25132513
asBytes: function () {
25142514
return Uint8Array.of(0);
25152515
}
@@ -2522,12 +2522,12 @@ describe('Eth calls using MirrorNode', async function () {
25222522
"gas": maxGasLimitHex
25232523
}, 'latest');
25242524

2525-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, undefined, maxGasLimit, accountAddress1, 'eth_call');
2525+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, undefined, maxGasLimit, accountAddress1, 'eth_call');
25262526
expect(result).to.equal("0x00");
25272527
});
25282528

25292529
it('eth_call with no from address', async function () {
2530-
sdkClientStub.submitContractCallQuery.returns({
2530+
sdkClientStub.submitContractCallQueryWithRetry.returns({
25312531
asBytes: function () {
25322532
return Uint8Array.of(0);
25332533
}
@@ -2540,12 +2540,12 @@ describe('Eth calls using MirrorNode', async function () {
25402540
"gas": maxGasLimitHex
25412541
}, 'latest');
25422542

2543-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, undefined, 'eth_call');
2543+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, undefined, 'eth_call');
25442544
expect(result).to.equal("0x00");
25452545
});
25462546

25472547
it('eth_call with all fields', async function () {
2548-
sdkClientStub.submitContractCallQuery.returns({
2548+
sdkClientStub.submitContractCallQueryWithRetry.returns({
25492549
asBytes: function () {
25502550
return Uint8Array.of(0);
25512551
}
@@ -2559,13 +2559,13 @@ describe('Eth calls using MirrorNode', async function () {
25592559
"gas": maxGasLimitHex
25602560
}, 'latest');
25612561

2562-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
2562+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
25632563
expect(result).to.equal("0x00");
25642564
});
25652565

25662566
//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.
25672567
it('eth_call should cache the response for 200ms', async function () {
2568-
sdkClientStub.submitContractCallQuery.returns({
2568+
sdkClientStub.submitContractCallQueryWithRetry.returns({
25692569
asBytes: function () {
25702570
return Uint8Array.of(0);
25712571
}
@@ -2601,7 +2601,7 @@ describe('Eth calls using MirrorNode', async function () {
26012601

26022602
describe('with gas > 15_000_000', async function() {
26032603
it('caps gas at 15_000_000', async function () {
2604-
sdkClientStub.submitContractCallQuery.returns({
2604+
sdkClientStub.submitContractCallQueryWithRetry.returns({
26052605
asBytes: function () {
26062606
return Uint8Array.of(0);
26072607
}
@@ -2615,13 +2615,13 @@ describe('Eth calls using MirrorNode', async function () {
26152615
"gas": 50_000_000
26162616
}, 'latest');
26172617

2618-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, 15_000_000, accountAddress1, 'eth_call');
2618+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, 15_000_000, accountAddress1, 'eth_call');
26192619
expect(result).to.equal("0x00");
26202620
});
26212621
});
26222622

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

26262626
const result = await ethImpl.call({
26272627
"from": accountAddress1,
@@ -2754,7 +2754,7 @@ describe('Eth calls using MirrorNode', async function () {
27542754
}
27552755
});
27562756

2757-
sdkClientStub.submitContractCallQuery.returns({
2757+
sdkClientStub.submitContractCallQueryWithRetry.returns({
27582758
asBytes: function () {
27592759
return Uint8Array.of(0);
27602760
}
@@ -2763,7 +2763,7 @@ describe('Eth calls using MirrorNode', async function () {
27632763

27642764
const result = await ethImpl.call(callData, 'latest');
27652765

2766-
sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
2766+
sinon.assert.calledWith(sdkClientStub.submitContractCallQueryWithRetry, contractAddress2, contractCallData, maxGasLimit, accountAddress1, 'eth_call');
27672767
expect(result).to.equal("0x00");
27682768
});
27692769

0 commit comments

Comments
 (0)