Skip to content

Commit aedfed7

Browse files
cherry pick #880 - simple cache for eth_call (#885)
* Add simple cache on `eth_call` (#880) Adds a cache on eth_call request, based on `to` and `data` parameters for 200ms. Signed-off-by: georgi-l95 <[email protected]> * retrigger checks --------- Signed-off-by: georgi-l95 <[email protected]> Co-authored-by: Georgi Lazarov <[email protected]>
1 parent 4b10b0a commit aedfed7

File tree

7 files changed

+62
-3
lines changed

7 files changed

+62
-3
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ GAS_PRICE_TINY_BAR_BUFFER =
2424
MIRROR_NODE_LIMIT_PARAM =
2525
CLIENT_TRANSPORT_SECURITY=
2626
INPUT_SIZE_LIMIT=
27-
ETH_CALL_CONSENSUS=
27+
ETH_CALL_CONSENSUS=
28+
ETH_CALL_CACHE_TTL=

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ MIRROR_NODE_RETRIES = 3
9595
MIRROR_NODE_RETRY_DELAY = 500
9696
MIRROR_NODE_LIMIT_PARAM = 100
9797
INPUT_SIZE_LIMIT = 1
98+
ETH_CALL_CACHE_TTL = 200
9899
````
99100

100101
Note: Read more about `DEV_MODE` [here](docs/dev-mode.md)

helm-chart/templates/configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ data:
3131
MIRROR_NODE_RETRY_DELAY: {{ .Values.config.MIRROR_NODE_RETRY_DELAY | quote }}
3232
MIRROR_NODE_LIMIT_PARAM: {{ .Values.config.MIRROR_NODE_LIMIT_PARAM | quote }}
3333
INPUT_SIZE_LIMIT: {{ .Values.config.INPUT_SIZE_LIMIT | quote }}
34+
ETH_CALL_CACHE_TTL: {{ .Values.config.ETH_CALL_CACHE_TTL | quote }}

helm-chart/value-test.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ config:
120120
MIRROR_NODE_RETRY_DELAY: 500
121121
MIRROR_NODE_LIMIT_PARAM: 100
122122
INPUT_SIZE_LIMIT: 1
123-
123+
ETH_CALL_CACHE_TTL: 200
124+
124125
# Enable rolling_restarts if SDK calls fail this is usually due to stale connections that get cycled on restart
125126
rolling_restart:
126127
enabled: false

helm-chart/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ config:
121121
MIRROR_NODE_LIMIT_PARAM: 100
122122
CLIENT_TRANSPORT_SECURITY: false
123123
INPUT_SIZE_LIMIT: 1
124+
ETH_CALL_CACHE_TTL: 200
124125

125126
# Enable rolling_restarts if SDK calls fail this is usually due to stale connections that get cycled on restart
126127
rolling_restart:

packages/relay/src/lib/eth.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { MirrorNodeClientError } from './errors/MirrorNodeClientError';
3131
import constants from './constants';
3232
import { Precheck } from './precheck';
3333
import { formatRequestIdMessage } from '../formatters';
34+
import crypto from 'crypto';
3435
const LRU = require('lru-cache');
3536
const _ = require('lodash');
3637
const createHash = require('keccak');
@@ -64,6 +65,7 @@ export class EthImpl implements Eth {
6465
static redirectBytecodePostfix = '600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033';
6566
static iHTSAddress = '0x0000000000000000000000000000000000000167';
6667
static invalidEVMInstruction = '0xfe';
68+
static ethCallCacheTtl = process.env.ETH_CALL_CACHE_TTL || 200;
6769

6870
// endpoint metric callerNames
6971
static ethCall = 'eth_call';
@@ -1017,8 +1019,24 @@ export class EthImpl implements Eth {
10171019
}
10181020
}
10191021

1022+
let data = call.data;
1023+
if (data) {
1024+
data = crypto.createHash('sha1').update(call.data).digest('hex'); // NOSONAR
1025+
}
1026+
1027+
const cacheKey = `eth_call:.${call.to}.${data}`;
1028+
let cachedResponse = this.cache.get(cacheKey);
1029+
1030+
if (cachedResponse != undefined) {
1031+
this.logger.debug(`${requestIdPrefix} eth_call returned cached response: ${cachedResponse}`);
1032+
return cachedResponse;
1033+
}
1034+
10201035
const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId);
1021-
return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
1036+
const formattedCallReponse = EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
1037+
1038+
this.cache.set(cacheKey, formattedCallReponse, { ttl: EthImpl.ethCallCacheTtl });
1039+
return formattedCallReponse;
10221040

10231041
} catch (e: any) {
10241042
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit contractCallQuery`);

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2412,6 +2412,42 @@ describe('Eth calls using MirrorNode', async function () {
24122412
expect(result).to.equal("0x00");
24132413
});
24142414

2415+
//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.
2416+
it('eth_call should cache the response for 200ms', async function () {
2417+
sdkClientStub.submitContractCallQuery.returns({
2418+
asBytes: function () {
2419+
return Uint8Array.of(0);
2420+
}
2421+
}
2422+
);
2423+
2424+
for (let index = 0; index < 3; index++) {
2425+
const result = await ethImpl.call({
2426+
"from": contractAddress1,
2427+
"to": contractAddress2,
2428+
"data": contractCallData,
2429+
"gas": maxGasLimitHex
2430+
}, 'latest');
2431+
expect(result).to.equal("0x00");
2432+
await new Promise(r => setTimeout(r, 50));
2433+
}
2434+
2435+
sinon.resetBehavior();
2436+
await new Promise(r => setTimeout(r, 200));
2437+
try {
2438+
await ethImpl.call({
2439+
"from": contractAddress1,
2440+
"to": contractAddress2,
2441+
"data": contractCallData,
2442+
"gas": maxGasLimitHex
2443+
}, 'latest');
2444+
} catch (error) {
2445+
expect(error.code).to.equal(predefined.INTERNAL_ERROR().code);
2446+
expect(error.name).to.equal(predefined.INTERNAL_ERROR().name);
2447+
}
2448+
2449+
});
2450+
24152451
describe('with gas > 15_000_000', async function() {
24162452
it('caps gas at 15_000_000', async function () {
24172453
sdkClientStub.submitContractCallQuery.returns({

0 commit comments

Comments
 (0)