Skip to content

Commit d9fc303

Browse files
lukelee-slgeorgi-l95Ivo-Yankov
authored
Cherry pick commits for v0.13.1 (#753)
* Check `to` parameter in `eth_call` method (#744) Checks if to parameter is passed in eth_call method, before checking it's length. Signed-off-by: georgi-l95 <[email protected]> * eth_getBalance workaround (#717) * feat: add workaround logic Signed-off-by: Ivo Yankov <[email protected]> * fix: unit tests Signed-off-by: Ivo Yankov <[email protected]> * chore: code cleanup Signed-off-by: Ivo Yankov <[email protected]> * test: add acceptance test Signed-off-by: Ivo Yankov <[email protected]> * chore: fix unit tests Signed-off-by: Ivo Yankov <[email protected]> * refactor: remove duplicated pagination util Signed-off-by: Ivo Yankov <[email protected]> * fix: add safeguard against double link prefix Signed-off-by: Ivo Yankov <[email protected]> * chore: fix acceptancetest api tests structure Signed-off-by: Ivo Yankov <[email protected]> * nit: code cleanup Signed-off-by: Ivo Yankov <[email protected]> * fix: calculate balance at block end Signed-off-by: Ivo Yankov <[email protected]> * fix: broken unit tests Signed-off-by: Ivo Yankov <[email protected]> * test: add explicit unit texts for balance calculation Signed-off-by: Ivo Yankov <[email protected]> * fix: tweak unit tests Signed-off-by: Ivo Yankov <[email protected]> * fix: failing acceptance test Signed-off-by: Ivo Yankov <[email protected]> * fix: failing acceptance test Signed-off-by: Ivo Yankov <[email protected]> * fix: failing tests Signed-off-by: Ivo Yankov <[email protected]> Signed-off-by: Ivo Yankov <[email protected]> Signed-off-by: georgi-l95 <[email protected]> Signed-off-by: Ivo Yankov <[email protected]> Co-authored-by: georgi-l95 <[email protected]> Co-authored-by: Ivo Yankov <[email protected]>
1 parent 8686666 commit d9fc303

File tree

8 files changed

+1030
-742
lines changed

8 files changed

+1030
-742
lines changed

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

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export class MirrorNodeClient {
6868
private static GET_NETWORK_EXCHANGERATE_ENDPOINT = 'network/exchangerate';
6969
private static GET_NETWORK_FEES_ENDPOINT = 'network/fees';
7070
private static GET_TOKENS_ENDPOINT = 'tokens';
71+
private static GET_TRANSACTIONS_ENDPOINT = 'transactions';
72+
73+
private static CONTRACT_RESULT_LOGS_PROPERTY = 'logs';
7174

7275
private static ORDER = {
7376
ASC: 'asc',
@@ -196,6 +199,23 @@ export class MirrorNodeClient {
196199
throw new MirrorNodeClientError(error.message, effectiveStatusCode);
197200
}
198201

202+
async getPaginatedResults(url: string, pathLabel: string, resultProperty: string, allowedErrorStatuses?: number[], requestId?: string, results = [], page = 1) {
203+
const result = await this.request(url, pathLabel, allowedErrorStatuses, requestId);
204+
205+
if (result && result[resultProperty]) {
206+
results = results.concat(result[resultProperty]);
207+
}
208+
209+
if (result && result.links?.next && page < constants.MAX_MIRROR_NODE_PAGINATION) {
210+
page++;
211+
const next = result.links.next.replace(constants.NEXT_LINK_PREFIX, "");
212+
return this.getPaginatedResults(next, pathLabel, resultProperty, allowedErrorStatuses, requestId, results, page);
213+
}
214+
else {
215+
return results;
216+
}
217+
}
218+
199219
public async getAccountLatestTransactionByAddress(idOrAliasOrEvmAddress: string, requestId?: string): Promise<object> {
200220
return this.request(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}?order=desc&limit=1`,
201221
MirrorNodeClient.GET_ACCOUNTS_ENDPOINT,
@@ -210,7 +230,23 @@ export class MirrorNodeClient {
210230
requestId);
211231
}
212232

213-
public async getBalanceAtTimestamp(accountId: string, timestamp: string, requestId?: string) {
233+
public async getTransactionsForAccount(accountId: string, timestampFrom: string, timestampTo: string, requestId?: string) {
234+
const queryParamObject = {};
235+
this.setQueryParam(queryParamObject, 'account.id', accountId);
236+
this.setQueryParam(queryParamObject, 'timestamp', `gte:${timestampFrom}`);
237+
this.setQueryParam(queryParamObject, 'timestamp', `lt:${timestampTo}`);
238+
const queryParams = this.getQueryParams(queryParamObject);
239+
240+
return this.getPaginatedResults(
241+
`${MirrorNodeClient.GET_TRANSACTIONS_ENDPOINT}${queryParams}`,
242+
MirrorNodeClient.GET_TRANSACTIONS_ENDPOINT,
243+
'transactions',
244+
[400, 404],
245+
requestId
246+
);
247+
}
248+
249+
public async getBalanceAtTimestamp(accountId: string, timestamp?: string, requestId?: string) {
214250
const queryParamObject = {};
215251
this.setQueryParam(queryParamObject, 'account.id', accountId);
216252
this.setQueryParam(queryParamObject, 'timestamp', timestamp);
@@ -316,10 +352,14 @@ export class MirrorNodeClient {
316352
limitOrderParams?: ILimitOrderParams,
317353
requestId?: string) {
318354
const queryParams = this.prepareLogsParams(contractLogsResultsParams, limitOrderParams);
319-
return this.request(`${MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT}${queryParams}`,
355+
356+
return this.getPaginatedResults(
357+
`${MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT}${queryParams}`,
320358
MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT,
359+
MirrorNodeClient.CONTRACT_RESULT_LOGS_PROPERTY,
321360
[400, 404],
322-
requestId);
361+
requestId
362+
);
323363
}
324364

325365
public async getContractResultsLogsByAddress(
@@ -333,41 +373,16 @@ export class MirrorNodeClient {
333373
MirrorNodeClient.ADDRESS_PLACEHOLDER,
334374
address
335375
);
336-
return this.request(`${apiEndpoint}${queryParams}`,
376+
377+
return this.getPaginatedResults(
378+
`${apiEndpoint}${queryParams}`,
337379
MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_BY_ADDRESS_ENDPOINT,
380+
MirrorNodeClient.CONTRACT_RESULT_LOGS_PROPERTY,
338381
[400, 404],
339-
requestId);
382+
requestId
383+
);
340384
}
341385

342-
public async getContractResultsLogsByNextLink(
343-
link: string,
344-
requestId?: string
345-
) {
346-
const nextLink = link.replace(constants.NEXT_LINK_PREFIX, '');
347-
return this.request(`${nextLink}`,
348-
MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT,
349-
[400, 404],
350-
requestId);
351-
}
352-
353-
public async pageAllResults(
354-
result: any,
355-
requestId?: string
356-
) {
357-
let unproccesedLogs = result.logs;
358-
if (result.links && result.links.next) {
359-
let nextLink = result.links.next;
360-
while (nextLink) {
361-
let nextResult = await this.getContractResultsLogsByNextLink(nextLink, requestId);
362-
if (!nextResult || !nextResult.logs) {
363-
break;
364-
}
365-
unproccesedLogs = unproccesedLogs.concat(nextResult.logs);
366-
nextLink = nextResult.links.next;
367-
}
368-
}
369-
return unproccesedLogs;
370-
}
371386

372387
public async getLatestBlock(requestId?: string) {
373388
return this.getBlocks(undefined, undefined, this.getLimitOrderQueryParam(1, MirrorNodeClient.ORDER.DESC), requestId);
@@ -461,7 +476,14 @@ export class MirrorNodeClient {
461476

462477
setQueryParam(queryParamObject, key, value) {
463478
if (key && value !== undefined) {
464-
queryParamObject[key] = value;
479+
if (!queryParamObject[key]) {
480+
queryParamObject[key] = value;
481+
}
482+
483+
// Allow for duplicating params
484+
else {
485+
queryParamObject[key] += `&${key}=${value}`;
486+
}
465487
}
466488
}
467489

packages/relay/src/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default {
6060
TX_DATA_ZERO_COST: 4,
6161
REQUEST_ID_STRING: `Request ID: `,
6262
BALANCES_UPDATE_INTERVAL: 900, // 15 minutes
63+
MAX_MIRROR_NODE_PAGINATION: 20,
6364
MIRROR_NODE_QUERY_LIMIT: 100,
6465
NEXT_LINK_PREFIX: '/api/v1/'
6566
};

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,6 @@ export const predefined = {
135135
code: -32606,
136136
message: 'HBAR Rate limit exceeded'
137137
}),
138-
'UNKNOWN_HISTORICAL_BALANCE': new JsonRpcError({
139-
name: 'Unavailable balance',
140-
code: -32007,
141-
message: 'Historical balance data is available only after 15 minutes.'
142-
}),
143138
'CONTRACT_REVERT': (errorMessage?: string) => new JsonRpcError({
144139
name: 'Contract revert executed',
145140
code: -32008,

packages/relay/src/lib/eth.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -570,10 +570,33 @@ export class EthImpl implements Eth {
570570
const latestTimestamp = Number(latestBlock.timestamp.from.split('.')[0]);
571571
const blockTimestamp = Number(block.timestamp.from.split('.')[0]);
572572
const timeDiff = latestTimestamp - blockTimestamp;
573-
574573
// The block is from the last 15 minutes, therefore the historical balance hasn't been imported in the Mirror Node yet
575574
if (timeDiff < constants.BALANCES_UPDATE_INTERVAL) {
576-
throw predefined.UNKNOWN_HISTORICAL_BALANCE;
575+
let currentBalance = 0;
576+
let currentTimestamp;
577+
let balanceFromTxs = 0;
578+
if (mirrorAccount.balance) {
579+
currentBalance = mirrorAccount.balance.balance;
580+
currentTimestamp = mirrorAccount.balance.timestamp;
581+
}
582+
583+
let transactionsInTimeWindow = await this.mirrorNodeClient.getTransactionsForAccount(
584+
mirrorAccount.account,
585+
block.timestamp.to,
586+
currentTimestamp,
587+
requestId
588+
);
589+
590+
for(const tx of transactionsInTimeWindow) {
591+
for (const transfer of tx.transfers) {
592+
if (transfer.account === mirrorAccount.account && !transfer.is_approval) {
593+
balanceFromTxs += transfer.amount;
594+
}
595+
}
596+
}
597+
598+
balanceFound = true;
599+
weibars = (currentBalance - balanceFromTxs) * constants.TINYBAR_TO_WEIBAR_COEF;
577600
}
578601

579602
// The block is NOT from the last 15 minutes, use /balances rest API
@@ -894,10 +917,9 @@ export class EthImpl implements Eth {
894917
const requestIdPrefix = formatRequestIdMessage(requestId);
895918
this.logger.trace(`${requestIdPrefix} call(hash=${JSON.stringify(call)}, blockParam=${blockParam})`, call, blockParam);
896919
// The "to" address must always be 42 chars.
897-
if (call.to.length != 42) {
898-
throw new Error(requestIdPrefix+
899-
" Invalid Contract Address: '" + call.to + "'. Expected length of 42 chars but was" + call.to.length
900-
);
920+
if (!call.to || call.to.length != 42) {
921+
const callToExist = call.to && call.to.length ? ` Expected length of 42 chars but was ${call.to.length}.` : '';
922+
throw new Error(`${requestIdPrefix}Invalid Contract Address: '${call.to}'.${callToExist}`);
901923
}
902924

903925
try {
@@ -1348,22 +1370,20 @@ export class EthImpl implements Eth {
13481370
}
13491371
}
13501372

1351-
let result;
1373+
let results;
13521374
if (address) {
1353-
result = await this.mirrorNodeClient.getContractResultsLogsByAddress(address, params, undefined, requestId);
1375+
results = await this.mirrorNodeClient.getContractResultsLogsByAddress(address, params, undefined, requestId);
13541376
}
13551377
else {
1356-
result = await this.mirrorNodeClient.getContractResultsLogs(params, undefined, requestId);
1378+
results = await this.mirrorNodeClient.getContractResultsLogs(params, undefined, requestId);
13571379
}
13581380

1359-
if (!result || !result.logs) {
1381+
if (!results) {
13601382
return [];
13611383
}
13621384

1363-
const unproccesedLogs = await this.mirrorNodeClient.pageAllResults(result, requestId);
1364-
13651385
const logs: Log[] = [];
1366-
for(const log of unproccesedLogs) {
1386+
for(const log of results) {
13671387
logs.push(
13681388
new Log({
13691389
address: await this.getLogEvmAddress(log.address, requestId) || log.address,

packages/relay/tests/helpers.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const mockData = {
8383
},
8484

8585
tokenId: '0.0.13312',
86+
tokenLongZero: '0x0000000000000000000000000000000000003400',
8687
token: {
8788
"admin_key": {
8889
"_type": "ProtobufEncoded",
@@ -503,4 +504,43 @@ export const defaultDetailedContractResultByHash = {
503504
"nonce": 1
504505
};
505506

507+
export const buildCryptoTransferTransaction = (from, to, amount, args: any = {}) => {
508+
return {
509+
"bytes": null,
510+
"charged_tx_fee": 2116872,
511+
"consensus_timestamp": args.timestamp || "1669207658.365113311",
512+
"entity_id": null,
513+
"max_fee": "100000000",
514+
"memo_base64": "UmVsYXkgdGVzdCB0b2tlbiB0cmFuc2Zlcg==",
515+
"name": "CRYPTOTRANSFER",
516+
"node": "0.0.8",
517+
"nonce": 0,
518+
"parent_consensus_timestamp": null,
519+
"result": "SUCCESS",
520+
"scheduled": false,
521+
"token_transfers": [],
522+
"transaction_hash": args.transactionHash || "OpCU4upAgJEBv2bjaoIurl4UYI4tuNA44ChtlKj+l0g0EvKbBpVI7lmnzeswVibQ",
523+
"transaction_id": args.transactionId || "0.0.28527683-1669207645-620109637",
524+
"transfers": [
525+
{
526+
"account": "0.0.8",
527+
"amount": 99202,
528+
"is_approval": false
529+
},
530+
{
531+
"account": from,
532+
"amount": -1 * amount,
533+
"is_approval": false
534+
},
535+
{
536+
"account": to,
537+
"amount": amount,
538+
"is_approval": false
539+
}
540+
],
541+
"valid_duration_seconds": "120",
542+
"valid_start_timestamp": "1669207645.620109637"
543+
}
544+
};
545+
506546
export const defaultErrorMessage = '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d53657420746f2072657665727400000000000000000000000000000000000000';

0 commit comments

Comments
 (0)