Skip to content

Commit

Permalink
ALL-2377 Fix loadbalancer & document status pages
Browse files Browse the repository at this point in the history
  • Loading branch information
Hathoriel committed Aug 10, 2023
1 parent 494a99d commit 03af20d
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 82 deletions.
139 changes: 77 additions & 62 deletions src/connector/tatum.connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { DefaultBodyType, DefaultParamsType, GetUrl, SdkRequest } from './connec
transient: true,
})
export class TatumConnector {

Check warning on line 13 in src/connector/tatum.connector.ts

View workflow job for this annotation

GitHub Actions / test

'TatumConnector' is defined but never used
constructor(private readonly id: string) {}
constructor(private readonly id: string) {
}

public async get<RESPONSE, PARAMS extends DefaultParamsType = DefaultParamsType>(request: GetUrl<PARAMS>) {
return this.request<RESPONSE, PARAMS>({ ...request, method: 'GET' })
Expand Down Expand Up @@ -40,54 +41,74 @@ export class TatumConnector {
retry = 0,
externalUrl?: string,
): Promise<RESPONSE> {
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,
body: body ? JSON.stringify(body) : null,
}

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<PARAMS extends DefaultParamsType = DefaultParamsType>({
path,
params,
basePath,
}: GetUrl<PARAMS>) {
path,
params,
basePath,
}: GetUrl<PARAMS>) {
const config = Container.of(this.id).get(CONFIG)
const url = new URL(
path || '',
Expand All @@ -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<RESPONSE>(url: string, request: RequestInit, response: Response, retry: number): Promise<RESPONSE> {
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,
Expand Down
2 changes: 1 addition & 1 deletion src/e2e/rpc/evm.e2e.utils.ts
Original file line number Diff line number Diff line change
@@ -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<BaseEvmClass>(RpcE2eUtils.initConfig(network)),
Expand Down
1 change: 0 additions & 1 deletion src/e2e/rpc/rpc.e2e.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export const RpcE2eUtils = {
network,
retryCount: 5,
retryDelay: 2000,
verbose: true,
apiKey: {
v2: process.env.V2_API_KEY
}
Expand Down
2 changes: 1 addition & 1 deletion src/e2e/rpc/tatum.rpc.optimism.spec.ts
Original file line number Diff line number Diff line change
@@ -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 })
})
Expand Down
38 changes: 23 additions & 15 deletions src/service/rpc/generic/LoadBalancerRpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -306,15 +306,21 @@ 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) {
const nodes: RpcNode[] = await archive.json()
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(
Expand All @@ -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
}
Expand All @@ -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({
Expand Down
3 changes: 1 addition & 2 deletions src/util/util.shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 03af20d

Please sign in to comment.