Skip to content

Commit d13e387

Browse files
Refactor eth_call to use mirror node (#763) (#863)
- Adds a new env variable that controls the behaviour of `eth_call`: `ETH_CALL_CONSENSUS` - Adds the option for `eth_call` to query the mirror node instead of consensus. - Adds logic to `mirrorNodeClient` for `POST` requests. - To enable mirror node integration set `ETH_CALL_CONSENSUS=false` --------- Signed-off-by: Ivo Yankov <[email protected]> Signed-off-by: lukelee-sl <[email protected]> Co-authored-by: Ivo Yankov <[email protected]>
1 parent 53eb8f7 commit d13e387

File tree

19 files changed

+1329
-777
lines changed

19 files changed

+1329
-777
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ MIRROR_NODE_RETRY_DELAY =
2323
GAS_PRICE_TINY_BAR_BUFFER =
2424
MIRROR_NODE_LIMIT_PARAM =
2525
CLIENT_TRANSPORT_SECURITY=
26-
INPUT_SIZE_LIMIT=
26+
INPUT_SIZE_LIMIT=
27+
ETH_CALL_CONSENSUS=

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

Lines changed: 87 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { Histogram, Registry } from 'prom-client';
2626
import { formatRequestIdMessage } from '../../formatters';
2727
import axiosRetry from 'axios-retry';
2828

29+
type REQUEST_METHODS = 'GET' | 'POST';
30+
2931
export interface ILimitOrderParams {
3032
limit?: number;
3133
order?: string;
@@ -70,6 +72,7 @@ export class MirrorNodeClient {
7072
private static GET_NETWORK_FEES_ENDPOINT = 'network/fees';
7173
private static GET_TOKENS_ENDPOINT = 'tokens';
7274
private static GET_TRANSACTIONS_ENDPOINT = 'transactions';
75+
private static CONTRACT_CALL_ENDPOINT = 'contracts/call';
7376

7477
private static CONTRACT_RESULT_LOGS_PROPERTY = 'logs';
7578
private static CONTRACT_STATE_PROPERTY = 'state';
@@ -87,9 +90,11 @@ export class MirrorNodeClient {
8790
*/
8891
private readonly logger: Logger;
8992

90-
private readonly client: AxiosInstance;
93+
private readonly restClient: AxiosInstance;
94+
private readonly web3Client: AxiosInstance;
9195

92-
public readonly baseUrl: string;
96+
public readonly restUrl: string;
97+
public readonly web3Url: string;
9398

9499
/**
95100
* The metrics register used for metrics tracking.
@@ -130,23 +135,23 @@ export class MirrorNodeClient {
130135
return axiosClient;
131136
}
132137

133-
constructor(baseUrl: string, logger: Logger, register: Registry, axiosClient?: AxiosInstance) {
134-
if (axiosClient !== undefined) {
135-
this.baseUrl = '';
136-
this.client = axiosClient;
137-
} else {
138-
if (!baseUrl.match(/^https?:\/\//)) {
139-
baseUrl = `https://${baseUrl}`;
140-
}
138+
constructor(restUrl: string, logger: Logger, register: Registry, restClient?: AxiosInstance, web3Url?: string, web3Client?: AxiosInstance) {
139+
if (!web3Url) {
140+
web3Url = restUrl;
141+
}
141142

142-
if (!baseUrl.match(/\/$/)) {
143-
baseUrl = `${baseUrl}/`;
144-
}
143+
if (restClient !== undefined) {
144+
this.restUrl = '';
145+
this.web3Url = '';
145146

146-
baseUrl = `${baseUrl}api/v1/`;
147+
this.restClient = restClient;
148+
this.web3Client = !!web3Client ? web3Client : restClient;
149+
} else {
150+
this.restUrl = this.buildUrl(restUrl);
151+
this.web3Url = this.buildUrl(web3Url);
147152

148-
this.baseUrl = baseUrl;
149-
this.client = axiosClient ? axiosClient : this.createAxiosClient(baseUrl);
153+
this.restClient = restClient ? restClient : this.createAxiosClient(this.restUrl);
154+
this.web3Client = web3Client ? web3Client : this.createAxiosClient(this.web3Url);
150155
}
151156

152157
this.logger = logger;
@@ -162,47 +167,77 @@ export class MirrorNodeClient {
162167
registers: [register]
163168
});
164169

165-
this.logger.info(`Mirror Node client successfully configured to ${this.baseUrl}`);
170+
this.logger.info(`Mirror Node client successfully configured to REST url: ${this.restUrl} and Web3 url: ${this.web3Url} `);
166171
}
172+
173+
private buildUrl(baseUrl: string) {
174+
if (!baseUrl.match(/^https?:\/\//)) {
175+
baseUrl = `https://${baseUrl}`;
176+
}
167177

168-
async request(path: string, pathLabel: string, allowedErrorStatuses?: number[], requestId?: string): Promise<any> {
178+
if (!baseUrl.match(/\/$/)) {
179+
baseUrl = `${baseUrl}/`;
180+
}
181+
182+
return `${baseUrl}api/v1/`;
183+
}
184+
185+
private async request(path: string, pathLabel: string, method: REQUEST_METHODS, data?: any, allowedErrorStatuses?: number[], requestId?: string): Promise<any> {
169186
const start = Date.now();
170187
const requestIdPrefix = formatRequestIdMessage(requestId);
171188
let ms;
172189
try {
173-
const response = await this.client.get(path, {
190+
let response;
191+
const headers = {
174192
headers:{
175193
'requestId': requestId || ''
176194
}
177-
});
178-
ms = Date.now() - start;
179-
this.logger.debug(`${requestIdPrefix} [GET] ${path} ${response.status} ${ms} ms`);
195+
};
196+
197+
if (method === 'GET') {
198+
response = await this.restClient.get(path, headers);
199+
}
200+
else {
201+
response = await this.web3Client.post(path, data, headers);
202+
}
203+
204+
const ms = Date.now() - start;
205+
this.logger.debug(`${requestIdPrefix} [${method}] ${path} ${response.status} ${ms} ms`);
180206
this.mirrorResponseHistogram.labels(pathLabel, response.status).observe(ms);
181207
return response.data;
182208
} catch (error: any) {
183209
ms = Date.now() - start;
184210
const effectiveStatusCode = error.response?.status || MirrorNodeClientError.ErrorCodes[error.code] || MirrorNodeClient.unknownServerErrorHttpStatusCode;
185211
this.mirrorResponseHistogram.labels(pathLabel, effectiveStatusCode).observe(ms);
186-
this.handleError(error, path, effectiveStatusCode, allowedErrorStatuses, requestId);
212+
this.handleError(error, path, effectiveStatusCode, method, allowedErrorStatuses, requestId);
187213
}
188214
return null;
189215
}
190216

191-
handleError(error: any, path: string, effectiveStatusCode: number, allowedErrorStatuses?: number[], requestId?: string) {
217+
async get(path: string, pathLabel: string, allowedErrorStatuses?: number[], requestId?: string): Promise<any> {
218+
return this.request(path, pathLabel, 'GET', null, allowedErrorStatuses, requestId);
219+
}
220+
221+
async post(path: string, data: any, pathLabel: string, allowedErrorStatuses?: number[], requestId?: string): Promise<any> {
222+
if (!data) data = {};
223+
return this.request(path, pathLabel, 'POST', data, allowedErrorStatuses, requestId);
224+
}
225+
226+
handleError(error: any, path: string, effectiveStatusCode: number, method: REQUEST_METHODS, allowedErrorStatuses?: number[], requestId?: string) {
192227
const requestIdPrefix = formatRequestIdMessage(requestId);
193228
if (allowedErrorStatuses && allowedErrorStatuses.length) {
194229
if (error.response && allowedErrorStatuses.indexOf(effectiveStatusCode) !== -1) {
195-
this.logger.debug(`${requestIdPrefix} [GET] ${path} ${effectiveStatusCode} status`);
230+
this.logger.debug(`${requestIdPrefix} [${method}] ${path} ${effectiveStatusCode} status`);
196231
return null;
197232
}
198233
}
199234

200-
this.logger.error(new Error(error.message), `${requestIdPrefix} [GET] ${path} ${effectiveStatusCode} status`);
235+
this.logger.error(new Error(error.message), `${requestIdPrefix} [${method}] ${path} ${effectiveStatusCode} status`);
201236
throw new MirrorNodeClientError(error.message, effectiveStatusCode);
202237
}
203238

204239
async getPaginatedResults(url: string, pathLabel: string, resultProperty: string, allowedErrorStatuses?: number[], requestId?: string, results = [], page = 1) {
205-
const result = await this.request(url, pathLabel, allowedErrorStatuses, requestId);
240+
const result = await this.get(url, pathLabel, allowedErrorStatuses, requestId);
206241

207242
if (result && result[resultProperty]) {
208243
results = results.concat(result[resultProperty]);
@@ -219,14 +254,14 @@ export class MirrorNodeClient {
219254
}
220255

221256
public async getAccountLatestTransactionByAddress(idOrAliasOrEvmAddress: string, requestId?: string): Promise<object> {
222-
return this.request(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}?order=desc&limit=1`,
257+
return this.get(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}?order=desc&limit=1`,
223258
MirrorNodeClient.GET_ACCOUNTS_ENDPOINT,
224259
[400],
225260
requestId);
226261
}
227262

228263
public async getAccount(idOrAliasOrEvmAddress: string, requestId?: string) {
229-
return this.request(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}`,
264+
return this.get(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}`,
230265
MirrorNodeClient.GET_ACCOUNTS_ENDPOINT,
231266
[400, 404],
232267
requestId);
@@ -253,14 +288,14 @@ export class MirrorNodeClient {
253288
this.setQueryParam(queryParamObject, 'account.id', accountId);
254289
this.setQueryParam(queryParamObject, 'timestamp', timestamp);
255290
const queryParams = this.getQueryParams(queryParamObject);
256-
return this.request(`${MirrorNodeClient.GET_BALANCE_ENDPOINT}${queryParams}`,
291+
return this.get(`${MirrorNodeClient.GET_BALANCE_ENDPOINT}${queryParams}`,
257292
MirrorNodeClient.GET_BALANCE_ENDPOINT,
258293
[400, 404],
259294
requestId);
260295
}
261296

262297
public async getBlock(hashOrBlockNumber: string | number, requestId?: string) {
263-
return this.request(`${MirrorNodeClient.GET_BLOCK_ENDPOINT}${hashOrBlockNumber}`,
298+
return this.get(`${MirrorNodeClient.GET_BLOCK_ENDPOINT}${hashOrBlockNumber}`,
264299
MirrorNodeClient.GET_BLOCK_ENDPOINT,
265300
[400, 404],
266301
requestId);
@@ -272,21 +307,21 @@ export class MirrorNodeClient {
272307
this.setQueryParam(queryParamObject, 'timestamp', timestamp);
273308
this.setLimitOrderParams(queryParamObject, limitOrderParams);
274309
const queryParams = this.getQueryParams(queryParamObject);
275-
return this.request(`${MirrorNodeClient.GET_BLOCKS_ENDPOINT}${queryParams}`,
310+
return this.get(`${MirrorNodeClient.GET_BLOCKS_ENDPOINT}${queryParams}`,
276311
MirrorNodeClient.GET_BLOCKS_ENDPOINT,
277312
[400, 404],
278313
requestId);
279314
}
280315

281316
public async getContract(contractIdOrAddress: string, requestId?: string) {
282-
return this.request(`${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${contractIdOrAddress}`,
317+
return this.get(`${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${contractIdOrAddress}`,
283318
MirrorNodeClient.GET_CONTRACT_ENDPOINT,
284319
[400, 404],
285320
requestId);
286321
}
287322

288323
public async getContractResult(transactionIdOrHash: string, requestId?: string) {
289-
return this.request(`${MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT}${transactionIdOrHash}`,
324+
return this.get(`${MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT}${transactionIdOrHash}`,
290325
MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT,
291326
[400, 404],
292327
requestId);
@@ -297,14 +332,14 @@ export class MirrorNodeClient {
297332
this.setContractResultsParams(queryParamObject, contractResultsParams);
298333
this.setLimitOrderParams(queryParamObject, limitOrderParams);
299334
const queryParams = this.getQueryParams(queryParamObject);
300-
return this.request(`${MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT}${queryParams}`,
335+
return this.get(`${MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT}${queryParams}`,
301336
MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT,
302337
[400, 404],
303338
requestId);
304339
}
305340

306341
public async getContractResultsDetails(contractId: string, timestamp: string, requestId?: string) {
307-
return this.request(`${this.getContractResultsDetailsByContractIdAndTimestamp(contractId, timestamp)}`,
342+
return this.get(`${this.getContractResultsDetailsByContractIdAndTimestamp(contractId, timestamp)}`,
308343
MirrorNodeClient.GET_CONTRACT_RESULTS_DETAILS_BY_CONTRACT_ID_ENDPOINT,
309344
[400, 404],
310345
requestId);
@@ -319,14 +354,14 @@ export class MirrorNodeClient {
319354
this.setContractResultsParams(queryParamObject, contractResultsParams);
320355
this.setLimitOrderParams(queryParamObject, limitOrderParams);
321356
const queryParams = this.getQueryParams(queryParamObject);
322-
return this.request(`${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}${queryParams}`,
357+
return this.get(`${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}${queryParams}`,
323358
MirrorNodeClient.GET_CONTRACT_RESULTS_BY_ADDRESS_ENDPOINT,
324359
[400],
325360
requestId);
326361
}
327362

328363
public async getContractResultsByAddressAndTimestamp(contractIdOrAddress: string, timestamp: string, requestId?: string) {
329-
return this.request(`${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}/${timestamp}`,
364+
return this.get(`${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}/${timestamp}`,
330365
MirrorNodeClient.GET_CONTRACT_RESULTS_BY_ADDRESS_ENDPOINT,
331366
[206, 400, 404],
332367
requestId);
@@ -398,7 +433,7 @@ export class MirrorNodeClient {
398433
const queryParamObject = {};
399434
this.setQueryParam(queryParamObject, 'timestamp', timestamp);
400435
const queryParams = this.getQueryParams(queryParamObject);
401-
return this.request(`${MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT}${queryParams}`,
436+
return this.get(`${MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT}${queryParams}`,
402437
MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT,
403438
[400, 404],
404439
requestId);
@@ -409,7 +444,7 @@ export class MirrorNodeClient {
409444
this.setQueryParam(queryParamObject, 'timestamp', timestamp);
410445
this.setQueryParam(queryParamObject, 'order', order);
411446
const queryParams = this.getQueryParams(queryParamObject);
412-
return this.request(`${MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT}${queryParams}`,
447+
return this.get(`${MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT}${queryParams}`,
413448
MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT,
414449
[400, 404],
415450
requestId);
@@ -426,7 +461,7 @@ export class MirrorNodeClient {
426461
}
427462

428463
public async getTokenById(tokenId: string, requestId?: string) {
429-
return this.request(`${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}`,
464+
return this.get(`${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}`,
430465
MirrorNodeClient.GET_TOKENS_ENDPOINT,
431466
[400, 404],
432467
requestId);
@@ -449,12 +484,16 @@ export class MirrorNodeClient {
449484
this.setLimitOrderParams(queryParamObject, limitOrderParams);
450485
const queryParams = this.getQueryParams(queryParamObject);
451486

452-
return this.request(`${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${address}${MirrorNodeClient.GET_STATE_ENDPOINT}${queryParams}`,
487+
return this.get(`${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${address}${MirrorNodeClient.GET_STATE_ENDPOINT}${queryParams}`,
453488
MirrorNodeClient.GET_STATE_ENDPOINT,
454489
[400, 404],
455490
requestId);
456491
}
457492

493+
public async postContractCall(callData: string, requestId?: string) {
494+
return this.post(MirrorNodeClient.CONTRACT_CALL_ENDPOINT, callData, MirrorNodeClient.CONTRACT_CALL_ENDPOINT, [400], requestId);
495+
}
496+
458497
getQueryParams(params: object) {
459498
let paramString = '';
460499
for (const [key, value] of Object.entries(params)) {
@@ -557,7 +596,11 @@ export class MirrorNodeClient {
557596
}
558597

559598
//exposing mirror node instance for tests
560-
public getMirrorNodeInstance(){
561-
return this.client;
599+
public getMirrorNodeRestInstance(){
600+
return this.restClient;
601+
}
602+
603+
public getMirrorNodeWeb3Instance(){
604+
return this.web3Client;
562605
}
563606
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,13 @@ export const predefined = {
160160
name: 'Value too low',
161161
code: -32602,
162162
message: 'Value below 10_000_000_000 wei which is 1 tinybar'
163-
})
163+
}),
164+
'INVALID_CONTRACT_ADDRESS': (address) => {
165+
const message = address && address.length ? ` Expected length of 42 chars but was ${address.length}.` : ''
166+
return new JsonRpcError({
167+
name: 'Invalid Contract Address',
168+
code: -32012,
169+
message: `Invalid Contract Address: ${address}.${message}`
170+
})
171+
}
164172
};

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export class MirrorNodeClientError extends Error {
2525
static retryErrorCodes: Array<number> = [400, 404, 408, 425, 500]
2626

2727
static ErrorCodes = {
28-
ECONNABORTED: 504
28+
ECONNABORTED: 504,
29+
CONTRACT_REVERT_EXECUTED : 400
2930
};
3031

3132
static statusCodes = {

0 commit comments

Comments
 (0)