Skip to content

Commit 88af656

Browse files
authored
HotFix to re-create the sdk client every X amount of request on runt… (#1235)
* HotFix to re-create the sdk client every X amount of request on runtime. Signed-off-by: Alfredo Gutierrez <[email protected]> * small improvement on logger Signed-off-by: Alfredo Gutierrez <[email protected]> * refactor to avoid duplicated code and tests fix Signed-off-by: Alfredo Gutierrez <[email protected]> * some minor improvements and added a test condition for mocked sdk to be used only on tests, as a workaround so test don't fail Signed-off-by: Alfredo Gutierrez <[email protected]> * removed unused imports Signed-off-by: Alfredo Gutierrez <[email protected]> * improvements from code review Signed-off-by: Alfredo Gutierrez <[email protected]> * removed some white spaces Signed-off-by: Alfredo Gutierrez <[email protected]> * renamed variable for more descriptive name and also, for making sure there is no other usages of it. Signed-off-by: Alfredo Gutierrez <[email protected]> Signed-off-by: Alfredo Gutierrez <[email protected]> --------- Signed-off-by: Alfredo Gutierrez <[email protected]>
1 parent 1b06725 commit 88af656

File tree

5 files changed

+94
-58
lines changed

5 files changed

+94
-58
lines changed

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
EthereumTransactionData,
4747
PrecheckStatusError,
4848
TransactionRecordQuery,
49-
Hbar,
49+
Hbar, PrivateKey,
5050
} from '@hashgraph/sdk';
5151
import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
5252
import { Logger } from "pino";
@@ -56,13 +56,59 @@ import HbarLimit from '../hbarlimiter';
5656
import constants from './../constants';
5757
import { SDKClientError } from './../errors/SDKClientError';
5858
import { JsonRpcError, predefined } from './../errors/JsonRpcError';
59+
import {RelayImpl} from "../relay";
5960

6061
const _ = require('lodash');
6162

6263
export class SDKClient {
6364
static transactionMode = 'TRANSACTION';
6465
static queryMode = 'QUERY';
6566
static recordMode = 'RECORD';
67+
68+
static initClient(logger: Logger, hederaNetwork: string, type: string | null = null): Client {
69+
let client: Client;
70+
if (hederaNetwork in RelayImpl.chainIds) {
71+
client = Client.forName(hederaNetwork);
72+
} else {
73+
client = Client.forNetwork(JSON.parse(hederaNetwork));
74+
}
75+
76+
if (type === 'eth_sendRawTransaction') {
77+
if (
78+
process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION &&
79+
process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION
80+
) {
81+
client = client.setOperator(
82+
AccountId.fromString(
83+
process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION
84+
),
85+
PrivateKey.fromString(
86+
process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION
87+
)
88+
);
89+
} else {
90+
logger.warn(`Invalid 'ETH_SENDRAWTRANSACTION' env variables provided`);
91+
}
92+
} else {
93+
if (process.env.OPERATOR_ID_MAIN && process.env.OPERATOR_KEY_MAIN) {
94+
client = client.setOperator(
95+
AccountId.fromString(process.env.OPERATOR_ID_MAIN.trim()),
96+
PrivateKey.fromString(process.env.OPERATOR_KEY_MAIN)
97+
);
98+
} else {
99+
logger.warn(`Invalid 'OPERATOR' env variables provided`);
100+
}
101+
}
102+
103+
client.setTransportSecurity(process.env.CLIENT_TRANSPORT_SECURITY === 'true' || false);
104+
client.setRequestTimeout(parseInt(process.env.SDK_REQUEST_TIMEOUT || '10000'));
105+
106+
logger.info(`SDK client successfully configured to ${JSON.stringify(hederaNetwork)} for account ${client.operatorAccountId} with request timeout value: ${process.env.SDK_REQUEST_TIMEOUT}`);
107+
108+
return client;
109+
}
110+
111+
66112
/**
67113
* The client to use for connecting to the main consensus network. The account
68114
* associated with this client will pay for all operations on the main network.

packages/relay/src/lib/eth.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020

2121
import { Eth } from '../index';
22-
import { Hbar, EthereumTransaction } from '@hashgraph/sdk';
22+
import {Hbar, EthereumTransaction} from '@hashgraph/sdk';
2323
import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
2424
import {BigNumber as BN} from "bignumber.js";
2525
import { Logger } from 'pino';
@@ -32,6 +32,7 @@ import constants from './constants';
3232
import { Precheck } from './precheck';
3333
import { formatRequestIdMessage } from '../formatters';
3434
import crypto from 'crypto';
35+
import {Registry} from "prom-client";
3536
const LRU = require('lru-cache');
3637
const _ = require('lodash');
3738
const createHash = require('keccak');
@@ -111,7 +112,7 @@ export class EthImpl implements Eth {
111112
*
112113
* @private
113114
*/
114-
private readonly sdkClient: SDKClient;
115+
private readonly injectedSdkClient: SDKClient;
115116

116117
/**
117118
* The interface through which we interact with the mirror node
@@ -136,6 +137,7 @@ export class EthImpl implements Eth {
136137
* @private
137138
*/
138139
private readonly chain: string;
140+
private registry: Registry;
139141

140142
/**
141143
* Create a new Eth implementation.
@@ -149,17 +151,47 @@ export class EthImpl implements Eth {
149151
mirrorNodeClient: MirrorNodeClient,
150152
logger: Logger,
151153
chain: string,
154+
register: Registry,
152155
cache?
153156
) {
154-
this.sdkClient = nodeClient;
157+
this.injectedSdkClient = nodeClient;
155158
this.mirrorNodeClient = mirrorNodeClient;
156159
this.logger = logger;
157160
this.chain = chain;
158161
this.precheck = new Precheck(mirrorNodeClient, nodeClient, logger, chain);
159162
this.cache = cache;
163+
this.registry = register;
160164
if (!cache) this.cache = new LRU(this.options);
161165
}
162166

167+
/* Temporary code duplication for a temporary HotFix workaround while the definitive fix is implemented
168+
* Please remove initClient workaround, or better yet, do not let this be merged on MAIN, short-lived fix intended only for 0.23.1 version */
169+
private requestsPerSdkClient = 0;
170+
private maxRequestsPerSdkClient = process.env.ETH_CALL_MAX_REQUEST_PER_SDK_INSTANCE || 50;
171+
private hederaNetwork: string = (process.env.HEDERA_NETWORK || '{}').toLowerCase();
172+
private ethSdkClient: SDKClient | undefined;
173+
private isInTest = typeof global.it === 'function';
174+
175+
getSdkClient() {
176+
177+
// for unit tests we need to use the mocked sdk client using DI
178+
if(this.isInTest) {
179+
return this.injectedSdkClient;
180+
}
181+
182+
// if we have reached the max number of requests per sdk client instance, or if the sdk client instance is undefined, create a new one
183+
if (this.requestsPerSdkClient >= this.maxRequestsPerSdkClient || this.ethSdkClient == undefined) {
184+
const hederaClient = SDKClient.initClient(this.logger, this.hederaNetwork);
185+
this.ethSdkClient = new SDKClient(hederaClient, this.logger, this.registry);
186+
this.requestsPerSdkClient = 0;
187+
}
188+
189+
// increment the number of requests per sdk client instance
190+
this.requestsPerSdkClient++;
191+
192+
return this.ethSdkClient;
193+
}
194+
163195
/**
164196
* This method is implemented to always return an empty array. This is in alignment
165197
* with the behavior of Infura.
@@ -329,7 +361,7 @@ export class EthImpl implements Eth {
329361
networkFees = {
330362
fees: [
331363
{
332-
gas: await this.sdkClient.getTinyBarGasFee(callerName, requestId),
364+
gas: await this.getSdkClient().getTinyBarGasFee(callerName, requestId),
333365
'transaction_type': EthImpl.ethTxType
334366
}
335367
]
@@ -828,7 +860,7 @@ export class EthImpl implements Eth {
828860
}
829861
}
830862

831-
const bytecode = await this.sdkClient.getContractByteCode(0, 0, address, EthImpl.ethGetCode, requestId);
863+
const bytecode = await this.getSdkClient().getContractByteCode(0, 0, address, EthImpl.ethGetCode, requestId);
832864
return EthImpl.prepend0x(Buffer.from(bytecode).toString('hex'));
833865
} catch (e: any) {
834866
if (e instanceof SDKClientError) {
@@ -984,7 +1016,7 @@ export class EthImpl implements Eth {
9841016
try {
9851017
const result = await this.mirrorNodeClient.resolveEntityType(address, [constants.TYPE_ACCOUNT, constants.TYPE_CONTRACT], requestId);
9861018
if (result?.type === constants.TYPE_ACCOUNT) {
987-
const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount, requestId);
1019+
const accountInfo = await this.getSdkClient().getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount, requestId);
9881020
return EthImpl.numberTo0x(Number(accountInfo.ethereumNonce));
9891021
}
9901022
else if (result?.type === constants.TYPE_CONTRACT) {
@@ -1025,11 +1057,11 @@ export class EthImpl implements Eth {
10251057

10261058
const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex');
10271059
try {
1028-
const contractExecuteResponse = await this.sdkClient.submitEthereumTransaction(transactionBuffer, EthImpl.ethSendRawTransaction, requestId);
1060+
const contractExecuteResponse = await this.getSdkClient().submitEthereumTransaction(transactionBuffer, EthImpl.ethSendRawTransaction, requestId);
10291061

10301062
try {
10311063
// Wait for the record from the execution.
1032-
const record = await this.sdkClient.executeGetTransactionRecord(contractExecuteResponse, EthereumTransaction.name, EthImpl.ethSendRawTransaction, interactingEntity, requestId);
1064+
const record = await this.getSdkClient().executeGetTransactionRecord(contractExecuteResponse, EthereumTransaction.name, EthImpl.ethSendRawTransaction, interactingEntity, requestId);
10331065
if (!record) {
10341066
this.logger.warn(`${requestIdPrefix} No record retrieved`);
10351067
throw predefined.INTERNAL_ERROR();
@@ -1175,7 +1207,7 @@ export class EthImpl implements Eth {
11751207
return cachedResponse;
11761208
}
11771209

1178-
const contractCallResponse = await this.sdkClient.submitContractCallQueryWithRetry(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId);
1210+
const contractCallResponse = await this.getSdkClient().submitContractCallQueryWithRetry(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId);
11791211
const formattedCallReponse = EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
11801212

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

packages/relay/src/lib/relay.ts

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { MirrorNodeClient, SDKClient } from './clients';
3232
import { Registry } from 'prom-client';
3333

3434
export class RelayImpl implements Relay {
35-
private static chainIds = {
35+
static chainIds = {
3636
mainnet: 0x127,
3737
testnet: 0x128,
3838
previewnet: 0x129,
@@ -54,7 +54,7 @@ export class RelayImpl implements Relay {
5454
process.env.CHAIN_ID || RelayImpl.chainIds[hederaNetwork] || '298';
5555
const chainId = EthImpl.prepend0x(Number(configuredChainId).toString(16));
5656

57-
this.clientMain = this.initClient(logger, hederaNetwork);
57+
this.clientMain = SDKClient.initClient(logger, hederaNetwork);
5858

5959
this.web3Impl = new Web3Impl(this.clientMain);
6060
this.netImpl = new NetImpl(this.clientMain, chainId);
@@ -73,7 +73,7 @@ export class RelayImpl implements Relay {
7373
sdkClient,
7474
mirrorNodeClient,
7575
logger.child({ name: 'relay-eth' }),
76-
chainId);
76+
chainId, register);
7777

7878

7979
if (process.env.SUBSCRIPTIONS_ENABLED && process.env.SUBSCRIPTIONS_ENABLED === 'true') {
@@ -100,46 +100,4 @@ export class RelayImpl implements Relay {
100100
return this.subImpl;
101101
}
102102

103-
initClient(logger: Logger, hederaNetwork: string, type: string | null = null): Client {
104-
let client: Client;
105-
if (hederaNetwork in RelayImpl.chainIds) {
106-
client = Client.forName(hederaNetwork);
107-
} else {
108-
client = Client.forNetwork(JSON.parse(hederaNetwork));
109-
}
110-
111-
if (type === 'eth_sendRawTransaction') {
112-
if (
113-
process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION &&
114-
process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION
115-
) {
116-
client = client.setOperator(
117-
AccountId.fromString(
118-
process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION
119-
),
120-
PrivateKey.fromString(
121-
process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION
122-
)
123-
);
124-
} else {
125-
logger.warn(`Invalid 'ETH_SENDRAWTRANSACTION' env variables provided`);
126-
}
127-
} else {
128-
if (process.env.OPERATOR_ID_MAIN && process.env.OPERATOR_KEY_MAIN) {
129-
client = client.setOperator(
130-
AccountId.fromString(process.env.OPERATOR_ID_MAIN.trim()),
131-
PrivateKey.fromString(process.env.OPERATOR_KEY_MAIN)
132-
);
133-
} else {
134-
logger.warn(`Invalid 'OPERATOR' env variables provided`);
135-
}
136-
}
137-
138-
client.setTransportSecurity(process.env.CLIENT_TRANSPORT_SECURITY === 'true' || false);
139-
client.setRequestTimeout(parseInt(process.env.SDK_REQUEST_TIMEOUT || '10000'));
140-
141-
logger.info(`SDK client successfully configured to ${JSON.stringify(hederaNetwork)} for account ${client.operatorAccountId} with request timeout value: ${process.env.SDK_REQUEST_TIMEOUT}`);
142-
143-
return client;
144-
}
145103
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('Eth calls using MirrorNode', async function () {
110110
process.env.ETH_FEE_HISTORY_FIXED = 'false';
111111

112112
// @ts-ignore
113-
ethImpl = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a', cache);
113+
ethImpl = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a', registry, cache);
114114
});
115115

116116
this.afterAll(() => {
@@ -3609,7 +3609,7 @@ describe('Eth', async function () {
36093609
let ethImpl: EthImpl;
36103610
this.beforeAll(() => {
36113611
// @ts-ignore
3612-
ethImpl = new EthImpl(null, mirrorNodeInstance, logger);
3612+
ethImpl = new EthImpl(null, mirrorNodeInstance, logger, '0x12a', new Registry());
36133613
});
36143614

36153615
const contractEvmAddress = '0xd8db0b1dbf8ba6721ef5256ad5fe07d72d1d04b9';

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe("Open RPC Specification", function () {
109109
mirrorNodeInstance = new MirrorNodeClient(process.env.MIRROR_NODE_URL, logger.child({ name: `mirror-node` }), registry, instance);
110110
sdkClientStub = sinon.createStubInstance(SDKClient);
111111
// @ts-ignore
112-
ethImpl = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a');
112+
ethImpl = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a', registry);
113113

114114
// mocked data
115115
mock.onGet('blocks?limit=1&order=desc').reply(200, { blocks: [defaultBlock] });

0 commit comments

Comments
 (0)