Skip to content

Commit

Permalink
Add simple cache on eth_call (#880)
Browse files Browse the repository at this point in the history
Adds a cache on eth_call request, based on `to` and `data` parameters for 200ms.

Signed-off-by: georgi-l95 <glazarov95@gmail.com>
  • Loading branch information
georgi-l95 authored and lukelee-sl committed Feb 7, 2023
1 parent 4b10b0a commit b03d80f
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ GAS_PRICE_TINY_BAR_BUFFER =
MIRROR_NODE_LIMIT_PARAM =
CLIENT_TRANSPORT_SECURITY=
INPUT_SIZE_LIMIT=
ETH_CALL_CONSENSUS=
ETH_CALL_CONSENSUS=
ETH_CALL_CACHE_TTL=
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ MIRROR_NODE_RETRIES = 3
MIRROR_NODE_RETRY_DELAY = 500
MIRROR_NODE_LIMIT_PARAM = 100
INPUT_SIZE_LIMIT = 1
ETH_CALL_CACHE_TTL = 200
````

Note: Read more about `DEV_MODE` [here](docs/dev-mode.md)
Expand Down
1 change: 1 addition & 0 deletions helm-chart/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ data:
MIRROR_NODE_RETRY_DELAY: {{ .Values.config.MIRROR_NODE_RETRY_DELAY | quote }}
MIRROR_NODE_LIMIT_PARAM: {{ .Values.config.MIRROR_NODE_LIMIT_PARAM | quote }}
INPUT_SIZE_LIMIT: {{ .Values.config.INPUT_SIZE_LIMIT | quote }}
ETH_CALL_CACHE_TTL: {{ .Values.config.ETH_CALL_CACHE_TTL | quote }}
3 changes: 2 additions & 1 deletion helm-chart/value-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ config:
MIRROR_NODE_RETRY_DELAY: 500
MIRROR_NODE_LIMIT_PARAM: 100
INPUT_SIZE_LIMIT: 1

ETH_CALL_CACHE_TTL: 200

# Enable rolling_restarts if SDK calls fail this is usually due to stale connections that get cycled on restart
rolling_restart:
enabled: false
Expand Down
1 change: 1 addition & 0 deletions helm-chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ config:
MIRROR_NODE_LIMIT_PARAM: 100
CLIENT_TRANSPORT_SECURITY: false
INPUT_SIZE_LIMIT: 1
ETH_CALL_CACHE_TTL: 200

# Enable rolling_restarts if SDK calls fail this is usually due to stale connections that get cycled on restart
rolling_restart:
Expand Down
20 changes: 19 additions & 1 deletion packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { MirrorNodeClientError } from './errors/MirrorNodeClientError';
import constants from './constants';
import { Precheck } from './precheck';
import { formatRequestIdMessage } from '../formatters';
import crypto from 'crypto';
const LRU = require('lru-cache');
const _ = require('lodash');
const createHash = require('keccak');
Expand Down Expand Up @@ -64,6 +65,7 @@ export class EthImpl implements Eth {
static redirectBytecodePostfix = '600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033';
static iHTSAddress = '0x0000000000000000000000000000000000000167';
static invalidEVMInstruction = '0xfe';
static ethCallCacheTtl = process.env.ETH_CALL_CACHE_TTL || 200;

// endpoint metric callerNames
static ethCall = 'eth_call';
Expand Down Expand Up @@ -1017,8 +1019,24 @@ export class EthImpl implements Eth {
}
}

let data = call.data;
if (data) {
data = crypto.createHash('sha1').update(call.data).digest('hex'); // NOSONAR
}

const cacheKey = `eth_call:.${call.to}.${data}`;
let cachedResponse = this.cache.get(cacheKey);

if (cachedResponse != undefined) {
this.logger.debug(`${requestIdPrefix} eth_call returned cached response: ${cachedResponse}`);
return cachedResponse;
}

const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId);
return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
const formattedCallReponse = EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));

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

} catch (e: any) {
this.logger.error(e, `${requestIdPrefix} Failed to successfully submit contractCallQuery`);
Expand Down
36 changes: 36 additions & 0 deletions packages/relay/tests/lib/eth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2412,6 +2412,42 @@ describe('Eth calls using MirrorNode', async function () {
expect(result).to.equal("0x00");
});

//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.
it('eth_call should cache the response for 200ms', async function () {
sdkClientStub.submitContractCallQuery.returns({
asBytes: function () {
return Uint8Array.of(0);
}
}
);

for (let index = 0; index < 3; index++) {
const result = await ethImpl.call({
"from": contractAddress1,
"to": contractAddress2,
"data": contractCallData,
"gas": maxGasLimitHex
}, 'latest');
expect(result).to.equal("0x00");
await new Promise(r => setTimeout(r, 50));
}

sinon.resetBehavior();
await new Promise(r => setTimeout(r, 200));
try {
await ethImpl.call({
"from": contractAddress1,
"to": contractAddress2,
"data": contractCallData,
"gas": maxGasLimitHex
}, 'latest');
} catch (error) {
expect(error.code).to.equal(predefined.INTERNAL_ERROR().code);
expect(error.name).to.equal(predefined.INTERNAL_ERROR().name);
}

});

describe('with gas > 15_000_000', async function() {
it('caps gas at 15_000_000', async function () {
sdkClientStub.submitContractCallQuery.returns({
Expand Down

0 comments on commit b03d80f

Please sign in to comment.