Skip to content

Commit f162a63

Browse files
authored
Retry on failed request to mirror node (#679)
- Introduce axios-retry for retrying failed requests to the mirror node - Add environmental variables for `delay` and `retry` counts ( 0 `retry` counts disables `axios-retry` ) - Change hbar rate limiter test to one which spends more. This increases the speed, because it's hitting limiter faster. - Bump timeout for rata limiter acceptance tests, because ofter 240 seconds are not enough. - Bump hbar rate limiter duration in local acceptance test, because it's failing otherwise. - Set delays on several places, because some of the tests are hitting rate limit Signed-off-by: georgi-l95 <[email protected]>
1 parent 97ae22a commit f162a63

File tree

9 files changed

+133
-39
lines changed

9 files changed

+133
-39
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ HBAR_RATE_LIMIT_TINYBAR =
1717
HBAR_RATE_LIMIT_DURATION =
1818
RATE_LIMIT_DISABLED =
1919
ETH_GET_LOGS_BLOCK_RANGE_LIMIT =
20-
DEV_MODE =
20+
DEV_MODE =
21+
MIRROR_NODE_RETRIES =
22+
MIRROR_NODE_RETRY_DELAY =

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ HBAR_RATE_LIMIT_TINYBAR = 6000000000
8181
HBAR_RATE_LIMIT_DURATION = 60000
8282
RATE_LIMIT_DISABLED = false
8383
DEV_MODE = false
84+
MIRROR_NODE_RETRIES = 3
85+
MIRROR_NODE_RETRY_DELAY = 500
8486
```
8587

8688
Note: Read more about `DEV_MODE` [here](docs/dev-mode.md)
87-
8889
The following table highlights some initial configuration values to consider
8990

9091
| Config | Default | Description |

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { Logger } from "pino";
2424
import constants from './../constants';
2525
import { Histogram, Registry } from 'prom-client';
2626
import { formatRequestIdMessage } from '../../formatters';
27+
import axiosRetry from 'axios-retry';
2728

2829
export interface ILimitOrderParams {
2930
limit?: number;
@@ -104,7 +105,23 @@ export class MirrorNodeClient {
104105
},
105106
timeout: 10 * 1000
106107
});
107-
108+
//@ts-ignore
109+
axiosRetry(axiosClient, {
110+
retries: parseInt(process.env.MIRROR_NODE_RETRIES!) || 3,
111+
retryDelay: (retryCount, error) => {
112+
const request = error?.request?._header;
113+
const requestId = request ? request.split('\n')[3].substring(11,47) : '';
114+
const requestIdPrefix = formatRequestIdMessage(requestId);
115+
const delay = (parseInt(process.env.MIRROR_NODE_RETRY_DELAY!) || 500);
116+
this.logger.trace(`${requestIdPrefix} Retry delay ${delay} ms`);
117+
return delay;
118+
},
119+
retryCondition: (error) => {
120+
return !error?.response?.status || MirrorNodeClientError.retryErrorCodes.includes(error?.response?.status);
121+
},
122+
shouldResetTimeout: true
123+
});
124+
108125
return axiosClient;
109126
}
110127

@@ -148,7 +165,11 @@ export class MirrorNodeClient {
148165
const requestIdPrefix = formatRequestIdMessage(requestId);
149166
let ms;
150167
try {
151-
const response = await this.client.get(path);
168+
const response = await this.client.get(path, {
169+
headers:{
170+
'requestId': requestId || ''
171+
}
172+
});
152173
ms = Date.now() - start;
153174
this.logger.debug(`${requestIdPrefix} [GET] ${path} ${response.status} ${ms} ms`);
154175
this.mirrorResponseHistogram.labels(pathLabel, response.status).observe(ms);
@@ -436,4 +457,9 @@ export class MirrorNodeClient {
436457

437458
return null;
438459
}
460+
461+
//exposing mirror node instance for tests
462+
public getMirrorNodeInstance(){
463+
return this.client;
464+
}
439465
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
export class MirrorNodeClientError extends Error {
2323
public statusCode: number;
2424

25+
static retryErrorCodes: Array<number> = [400, 404, 408, 425, 500]
26+
2527
static ErrorCodes = {
2628
ECONNABORTED: 504
2729
};

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

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,12 @@ describe('Eth calls using MirrorNode', async function () {
8585
let ethImpl: EthImpl;
8686

8787
this.beforeAll(() => {
88-
// mock axios
89-
const instance = axios.create({
90-
baseURL: 'https://localhost:5551/api/v1',
91-
responseType: 'json' as const,
92-
headers: {
93-
'Content-Type': 'application/json'
94-
},
95-
timeout: 10 * 1000
96-
});
97-
9888
// @ts-ignore
99-
mock = new MockAdapter(instance, { onNoMatch: "throwException" });
89+
mirrorNodeInstance = new MirrorNodeClient(process.env.MIRROR_NODE_URL, logger.child({ name: `mirror-node` }), registry);
90+
10091
// @ts-ignore
101-
mirrorNodeInstance = new MirrorNodeClient(process.env.MIRROR_NODE_URL, logger.child({ name: `mirror-node` }), registry, instance);
92+
mock = new MockAdapter(mirrorNodeInstance.getMirrorNodeInstance(), { onNoMatch: "throwException" });
93+
10294
sdkClientStub = sinon.createStubInstance(SDKClient);
10395
cache = new LRU({
10496
max: constants.CACHE_MAX,
@@ -536,6 +528,65 @@ describe('Eth calls using MirrorNode', async function () {
536528
}
537529
});
538530

531+
it('"eth_blockNumber" return the latest block number on second try', async function () {
532+
mock.onGet('blocks?limit=1&order=desc').replyOnce(404, {
533+
'_status': {
534+
'messages': [
535+
{
536+
'message': 'Block not found'
537+
}
538+
]
539+
}
540+
}).onGet('blocks?limit=1&order=desc').replyOnce(200, {
541+
blocks: [defaultBlock]
542+
});
543+
544+
const blockNumber = await ethImpl.blockNumber();
545+
expect(blockNumber).to.be.eq(blockNumber);
546+
});
547+
548+
it('"eth_blockNumber" should throw an error if no blocks are found after third try', async function () {
549+
mock.onGet('blocks?limit=1&order=desc').replyOnce(404, {
550+
'_status': {
551+
'messages': [
552+
{
553+
'message': 'Block not found'
554+
}
555+
]
556+
}
557+
}).onGet('blocks?limit=1&order=desc').replyOnce(404, {
558+
'_status': {
559+
'messages': [
560+
{
561+
'message': 'Block not found'
562+
}
563+
]
564+
}
565+
}).onGet('blocks?limit=1&order=desc').replyOnce(404, {
566+
'_status': {
567+
'messages': [
568+
{
569+
'message': 'Block not found'
570+
}
571+
]
572+
}
573+
}).onGet('blocks?limit=1&order=desc').replyOnce(404, {
574+
'_status': {
575+
'messages': [
576+
{
577+
'message': 'Block not found'
578+
}
579+
]
580+
}
581+
});
582+
583+
try {
584+
await ethImpl.blockNumber();
585+
} catch (error: any) {
586+
expect(error.message).to.equal('Error encountered retrieving latest block');
587+
}
588+
});
589+
539590
it('eth_getBlockByNumber with match', async function () {
540591
// mirror node request mocks
541592
mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock);

packages/server/tests/acceptance/htsPrecompile/tokenCreate.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ describe('@tokencreate HTS Precompile Token Create Acceptance Tests', async func
173173
});
174174

175175
it('should associate to a token with custom fees', async function() {
176+
//delay for hbar rate limiter to reset
177+
await new Promise(r => setTimeout(r, parseInt(process.env.HBAR_RATE_LIMIT_DURATION!)));
178+
176179
const mainContractOwner = new ethers.Contract(mainContractAddress, TokenCreateJson.abi, accounts[0].wallet);
177180
const txCO = await mainContractOwner.associateTokenPublic(mainContractAddress, HTSTokenWithCustomFeesContractAddress, { gasLimit: 10000000 });
178181
expect((await txCO.wait()).events.filter(e => e.event === 'ResponseCode')[0].args.responseCode).to.equal(TX_SUCCESS_CODE);

packages/server/tests/acceptance/htsPrecompile/tokenManagement.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ describe('@tokenmanagement HTS Precompile Token Management Acceptance Tests', as
136136
let tokenAddress, tokenContract, nftAddress;
137137

138138
before(async function() {
139+
//delay for hbar rate limiter to reset
140+
await new Promise(r => setTimeout(r, parseInt(process.env.HBAR_RATE_LIMIT_DURATION!)));
141+
139142
// Create token and nft contracts
140143
tokenAddress = await createHTSToken();
141144
nftAddress = await createNftHTSToken();
@@ -562,8 +565,8 @@ describe('@tokenmanagement HTS Precompile Token Management Acceptance Tests', as
562565
expect(tokenExpiryInfoAfter.autoRenewPeriod).to.equal(expiryInfo.autoRenewPeriod);
563566
expect(newRenewAccountEvmAddress.toLowerCase()).to.equal(expectedRenewAddress.toLowerCase());
564567

565-
//use close to with delta 300 seconds, because we don't know the exact second it was set to expiry
566-
expect(tokenExpiryInfoAfter.second).to.be.closeTo(epoch, 300);
568+
//use close to with delta 400 seconds, because we don't know the exact second it was set to expiry
569+
expect(tokenExpiryInfoAfter.second).to.be.closeTo(epoch, 400);
567570
});
568571

569572
it('should be able to get and update non fungible token expiry info', async function() {
@@ -605,8 +608,8 @@ describe('@tokenmanagement HTS Precompile Token Management Acceptance Tests', as
605608
expect(tokenExpiryInfoAfter.autoRenewPeriod).to.equal(expiryInfo.autoRenewPeriod);
606609
expect(newRenewAccountEvmAddress.toLowerCase()).to.equal(expectedRenewAddress.toLowerCase());
607610

608-
//use close to with delta 300 seconds, because we don't know the exact second it was set to expiry
609-
expect(tokenExpiryInfoAfter.second).to.be.closeTo(epoch, 300);
611+
//use close to with delta 400 seconds, because we don't know the exact second it was set to expiry
612+
expect(tokenExpiryInfoAfter.second).to.be.closeTo(epoch, 400);
610613
});
611614
});
612615

packages/server/tests/acceptance/rateLimiter.spec.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ import { ContractFunctionParameters } from '@hashgraph/sdk';
2929
import parentContractJson from '../contracts/Parent.json';
3030
import { predefined } from '@hashgraph/json-rpc-relay/src/lib/errors/JsonRpcError';
3131
import { Utils } from '../helpers/utils';
32+
import BaseHTSJson from '../contracts/contracts_v1/BaseHTS.json';
3233

3334
describe('@ratelimiter Rate Limiters Acceptance Tests', function () {
34-
this.timeout(240 * 1000); // 240 seconds
35+
this.timeout(480 * 1000); // 480 seconds
3536

3637
const accounts: AliasAccount[] = [];
3738

@@ -87,15 +88,16 @@ describe('@ratelimiter Rate Limiters Acceptance Tests', function () {
8788
});
8889

8990
describe('HBAR Limiter Acceptance Tests', function () {
90-
this.timeout(240 * 1000); // 240 seconds
91+
this.timeout(480 * 1000); // 480 seconds
9192

9293
this.beforeAll(async () => {
9394
requestId = Utils.generateRequestId();
9495
const requestIdPrefix = Utils.formatRequestIdMessage(requestId);
9596

9697
logger.info(`${requestIdPrefix} Creating accounts`);
97-
accounts[0] = await servicesNode.createAliasAccount(15, null, requestId);
98-
accounts[1] = await servicesNode.createAliasAccount(100, null, requestId);
98+
logger.info(`${requestIdPrefix} HBAR_RATE_LIMIT_TINYBAR: ${process.env.HBAR_RATE_LIMIT_TINYBAR}`);
99+
accounts[0] = await servicesNode.createAliasAccount(15, relay.provider, requestId);
100+
accounts[1] = await servicesNode.createAliasAccount(200, relay.provider, requestId);
99101
contractId = await accounts[0].client.createParentContract(parentContractJson, requestId);
100102

101103
const params = new ContractFunctionParameters().addUint256(1);
@@ -126,22 +128,24 @@ describe('@ratelimiter Rate Limiters Acceptance Tests', function () {
126128
type: 2
127129
};
128130

131+
async function deployBaseHTSContract() {
132+
const baseHTSFactory = new ethers.ContractFactory(BaseHTSJson.abi, BaseHTSJson.bytecode, accounts[1].wallet);
133+
const baseHTS = await baseHTSFactory.deploy({gasLimit: 10_000_000});
134+
const { contractAddress } = await baseHTS.deployTransaction.wait();
135+
136+
return contractAddress;
137+
}
138+
129139
it('should fail to execute "eth_sendRawTransaction" due to HBAR rate limit exceeded ', async function () {
130140
await new Promise(r => setTimeout(r, parseInt(process.env.HBAR_RATE_LIMIT_DURATION!)));
141+
const requestIdPrefix = Utils.formatRequestIdMessage(requestId);
142+
131143
let rateLimit = false;
132144
try {
133145
for (let index = 0; index < parseInt(process.env.TIER_1_RATE_LIMIT!) * 2; index++) {
134-
const gasPrice = await relay.gasPrice(requestId);
135-
136-
const transaction = {
137-
...defaultLondonTransactionData,
138-
to: mirrorContract.evm_address,
139-
nonce: await relay.getAccountNonce(accounts[1].address, requestId),
140-
maxPriorityFeePerGas: gasPrice,
141-
maxFeePerGas: gasPrice,
142-
};
143-
const signedTx = await accounts[1].wallet.signTransaction(transaction);
144-
await relay.call('eth_sendRawTransaction', [signedTx], requestId);
146+
const BaseHTSContractAddress = await deployBaseHTSContract();
147+
const baseHTSContract = new ethers.Contract(BaseHTSContractAddress, BaseHTSJson.abi, accounts[0].wallet);
148+
logger.info(`${requestIdPrefix} Contract deployed to ${baseHTSContract.address}`);
145149
await new Promise(r => setTimeout(r, 1));
146150
}
147151
} catch (error) {

packages/server/tests/localAcceptance.env

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ TIER_1_RATE_LIMIT = 100
1919
TIER_2_RATE_LIMIT = 800
2020
TIER_3_RATE_LIMIT = 1600
2121
LIMIT_DURATION = 60000
22-
HBAR_RATE_LIMIT_TINYBAR = 15000000000
23-
HBAR_RATE_LIMIT_DURATION = 60000
24-
RATE_LIMIT_DISABLED=false
25-
DEV_MODE=false
22+
HBAR_RATE_LIMIT_TINYBAR = 11000000000
23+
HBAR_RATE_LIMIT_DURATION = 80000
24+
RATE_LIMIT_DISABLED = false
25+
DEV_MODE = false
26+
MIRROR_NODE_RETRIES = 3
27+
MIRROR_NODE_RETRY_DELAY = 500

0 commit comments

Comments
 (0)