From 03af20d1bf01997f6b74fbb321a815221f0f6285 Mon Sep 17 00:00:00 2001 From: Hathoriel Date: Wed, 9 Aug 2023 16:01:57 +0200 Subject: [PATCH] ALL-2377 Fix loadbalancer & document status pages --- src/connector/tatum.connector.ts | 139 ++++++++++++--------- src/e2e/rpc/evm.e2e.utils.ts | 2 +- src/e2e/rpc/rpc.e2e.utils.ts | 1 - src/e2e/rpc/tatum.rpc.optimism.spec.ts | 2 +- src/service/rpc/generic/LoadBalancerRpc.ts | 38 +++--- src/util/util.shared.ts | 3 +- 6 files changed, 103 insertions(+), 82 deletions(-) diff --git a/src/connector/tatum.connector.ts b/src/connector/tatum.connector.ts index 67a0a531c6..8d7a6ef640 100644 --- a/src/connector/tatum.connector.ts +++ b/src/connector/tatum.connector.ts @@ -11,7 +11,8 @@ import { DefaultBodyType, DefaultParamsType, GetUrl, SdkRequest } from './connec transient: true, }) export class TatumConnector { - constructor(private readonly id: string) {} + constructor(private readonly id: string) { + } public async get(request: GetUrl) { return this.request({ ...request, method: 'GET' }) @@ -40,10 +41,8 @@ export class TatumConnector { retry = 0, externalUrl?: string, ): Promise { - const { verbose } = Container.of(this.id).get(CONFIG) - const url = externalUrl || this.getUrl({ path, params, basePath }) - const headers = await Utils.getHeaders(this.id, retry) + const headers = await Utils.getHeaders(this.id) const request: RequestInit = { headers, method, @@ -51,43 +50,65 @@ export class TatumConnector { } const start = Date.now() - if (verbose) { - console.debug(new Date().toISOString(), 'Request: ', request.method, url, request.body) - } + try { - return await fetch(url, request).then(async (res) => { - const end = Date.now() - start - if (verbose) { - console.log( - new Date().toISOString(), - `Response received in ${end}ms: `, - res.status, - await res.clone().text(), - ) - } - if ([204, 304, 101, 100].includes(res.status)) { - return - } - - if (res.ok) { - return res.json() - } - - return this.retry(url, request, res) - }) - } catch (error) { - if (verbose) { - console.warn(new Date().toISOString(), `Error: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`) + const res = await fetch(url, request); + const end = Date.now() - start; + const responseBody = await res.clone().text(); + + // Structure your log entry here + Utils.log({ + id: this.id, + message: `Request & Response`, + data: { + request: { + method: request.method, + url: url, + body: request.body, + }, + response: { + status: res.status, + time: `${end}ms`, + body: responseBody, + }, + }, + }); + + if (res.ok) { + return await res.json(); } - return Promise.reject(error) + + // Retry only in case of 5xx error + if (res.status >= 500 && res.status < 600) { + return await this.retry(url, request, res, retry); + } + + return await Promise.reject(responseBody); + } catch (error) { + const end = Date.now() - start; + Utils.log({ + id: this.id, + message: `Error in Request & Response`, + data: { + request: { + method: request.method, + url: url, + body: request.body, + }, + error: JSON.stringify(error, Object.getOwnPropertyNames(error)), + time: `${end}ms`, + }, + }); + return Promise.reject(error); } } + private getUrl({ - path, - params, - basePath, - }: GetUrl) { + path, + params, + basePath, + }: GetUrl) { const config = Container.of(this.id).get(CONFIG) const url = new URL( path || '', @@ -107,40 +128,34 @@ export class TatumConnector { return url.toString() } - private async retry(url: string, request: RequestInit, response: Response) { - const { retryDelay, retryCount, verbose } = Container.of(this.id).get(CONFIG) + private async retry(url: string, request: RequestInit, response: Response, retry: number): Promise { + const { retryDelay, retryCount } = Container.of(this.id).get(CONFIG) if (!retryCount) { - if (verbose) { - console.warn( - new Date().toISOString(), - `Not retrying the request - no max retry count defined: `, - url, - request.body, - ) - } + Utils.log({ + id: this.id, + message: `Not retrying the request - no max retry count defined`, + data: { url, requestBody: request.body }, + }) return Promise.reject(await response.text()) } - const retry = parseInt(response.headers.get('x-ttm-sdk-retry') || `${retryCount}`) + 1 + if (retry >= retryCount) { - if (verbose) { - console.warn( - new Date().toISOString(), - `Not retrying the request for the '${retry}' time - exceeded max retry count ${retryCount}: `, - url, - request.body, - ) - } + Utils.log({ + id: this.id, + message: `Not retrying the request for the '${retry}' time - exceeded max retry count ${retryCount}: `, + data: { url, requestBody: request.body }, + }) return Promise.reject(await response.text()) } + + retry++; + await Utils.delay(retryDelay || 1000) - if (verbose) { - console.warn( - new Date().toISOString(), - `Retrying the request for the '${retry}' time: `, - url, - request.body, - ) - } + Utils.log({ + id: this.id, + message: `Retrying the request for the '${retry}' time: `, + data: { url, requestBody: request.body }, + }) return this.request( { method: request.method as string, diff --git a/src/e2e/rpc/evm.e2e.utils.ts b/src/e2e/rpc/evm.e2e.utils.ts index 4578798498..9c335486c3 100644 --- a/src/e2e/rpc/evm.e2e.utils.ts +++ b/src/e2e/rpc/evm.e2e.utils.ts @@ -1,7 +1,7 @@ import { Network } from '../../dto' import { BaseEvmClass, TatumSDK } from '../../service' import { RpcE2eUtils } from './rpc.e2e.utils' -import { BigNumber } from 'bignumber.js' + import { BigNumber } from 'bignumber.js' export const EvmE2eUtils = { initTatum: async (network: Network) => TatumSDK.init(RpcE2eUtils.initConfig(network)), diff --git a/src/e2e/rpc/rpc.e2e.utils.ts b/src/e2e/rpc/rpc.e2e.utils.ts index b98c28a468..f4b367fa7a 100644 --- a/src/e2e/rpc/rpc.e2e.utils.ts +++ b/src/e2e/rpc/rpc.e2e.utils.ts @@ -5,7 +5,6 @@ export const RpcE2eUtils = { network, retryCount: 5, retryDelay: 2000, - verbose: true, apiKey: { v2: process.env.V2_API_KEY } diff --git a/src/e2e/rpc/tatum.rpc.optimism.spec.ts b/src/e2e/rpc/tatum.rpc.optimism.spec.ts index 79ab329b7f..d620af295c 100644 --- a/src/e2e/rpc/tatum.rpc.optimism.spec.ts +++ b/src/e2e/rpc/tatum.rpc.optimism.spec.ts @@ -1,7 +1,7 @@ import { Network } from '../../service' import { EvmE2eUtils } from './evm.e2e.utils' -describe.skip('Optimism', () => { +describe('Optimism', () => { describe('mainnet', () => { EvmE2eUtils.e2e({ network: Network.OPTIMISM, chainId: 10 }) }) diff --git a/src/service/rpc/generic/LoadBalancerRpc.ts b/src/service/rpc/generic/LoadBalancerRpc.ts index 4005a7b629..2694f2ae31 100644 --- a/src/service/rpc/generic/LoadBalancerRpc.ts +++ b/src/service/rpc/generic/LoadBalancerRpc.ts @@ -139,7 +139,7 @@ export class LoadBalancerRpc implements AbstractRpcInterface { for (const server of this.rpcUrls[nodeType]) { Utils.log({ id: this.id, message: `Checking status of ${server.node.url}` }) all.push( - Utils.fetchWithTimeout(server.node.url, this.id,{ + Utils.fetchWithTimeout(server.node.url, this.id, { method: 'POST', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -306,7 +306,10 @@ export class LoadBalancerRpc implements AbstractRpcInterface { this.initRemoteHosts(RpcNodeType.NORMAL, nodes) this.initRemoteHosts(RpcNodeType.ARCHIVE, nodes) } else { - console.error(new Date().toISOString(), `Failed to fetch RPC configuration for ${network} blockchain for normal nodes`) + Utils.log({ + id: this.id, + message: `Failed to fetch RPC configuration for ${network} blockchain for normal nodes`, + }) } if (archive.ok) { @@ -314,7 +317,10 @@ export class LoadBalancerRpc implements AbstractRpcInterface { this.initRemoteHosts(RpcNodeType.NORMAL, nodes) this.initRemoteHosts(RpcNodeType.ARCHIVE, nodes) } else { - console.error(new Date().toISOString(), `Failed to fetch RPC configuration for ${network} blockchain for archive nodes`) + Utils.log({ + id: this.id, + message: `Failed to fetch RPC configuration for ${network} blockchain for archive nodes`, + }) } } catch (e) { console.error( @@ -325,20 +331,22 @@ export class LoadBalancerRpc implements AbstractRpcInterface { } async handleFailedRpcCall(rpcCall: JsonRpcCall | JsonRpcCall[], e: unknown, nodeType: RpcNodeType) { - const { verbose, rpc: rpcConfig } = Container.of(this.id).get(CONFIG) + const { rpc: rpcConfig } = Container.of(this.id).get(CONFIG) const { url } = this.getActiveUrl(nodeType) const activeIndex = this.getActiveIndex(nodeType) - if (verbose) { - console.warn( - new Date().toISOString(), - `Failed to call RPC ${ - Array.isArray(rpcCall) ? 'methods' : rpcCall.method - } on ${url}. Error: ${JSON.stringify(e, Object.getOwnPropertyNames(e))}`, - ) - console.log(new Date().toISOString(), `Switching to another server, marking this as unstable.`) - } + Utils.log({ + id: this.id, + message: `Failed to call RPC ${ + Array.isArray(rpcCall) ? 'methods' : rpcCall.method + } on ${url}. Error: ${JSON.stringify(e, Object.getOwnPropertyNames(e))}` + }) + + Utils.log({ + id: this.id, + message: `Switching to another server, marking ${url} as unstable.` + }) - if (!activeIndex) { + if (activeIndex == null) { console.error(`No active server found for node type ${NODE_TYPE_LABEL[nodeType]}.`) throw e } @@ -354,7 +362,7 @@ export class LoadBalancerRpc implements AbstractRpcInterface { rpcConfig?.allowedBlocksBehind as number, ) if (index === -1) { - console.error(`All servers are unavailable.`) + console.error(`All RPC nodes are unavailable.`) throw e } Utils.log({ diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index 71b2ba84cb..0736c41a42 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -215,14 +215,13 @@ export const Utils = { clearTimeout(id) return { responseTime, response } }, - getHeaders: (id: string, retry = 0) => { + getHeaders: (id: string) => { const config = Container.of(id).get(CONFIG) const headers = new Headers({ 'Content-Type': 'application/json', 'x-ttm-sdk-version': version, 'x-ttm-sdk-product': 'JS', 'x-ttm-sdk-debug': `${config.verbose}`, - 'x-ttm-sdk-retry': `${retry}`, }) if (config.apiKey) {