Skip to content

Feat: (Health check) Check alt_ip if web domain is not accessible #768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/config/utils/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NodeInfo } from '@/types/wallets'
import type { NodeInfo } from '@/types/wallets'
import { BlockchainSymbol } from './types'
import config from '../index'

Expand Down
38 changes: 36 additions & 2 deletions src/lib/nodes/abstract.node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { NodeInfo } from '@/types/wallets/index.ts'
import { getHealthCheckInterval } from './utils/getHealthcheckConfig'
import { TNodeLabel } from './constants'
import { HealthcheckInterval, HealthcheckResult, NodeKind, NodeStatus, NodeType } from './types'
Expand Down Expand Up @@ -29,6 +30,10 @@ export abstract class Node<C = unknown> {
*/
wsPort = '36668'

/**
* Node alternative IP
*/
altIp?: string
/**
* Node base URL
*/
Expand Down Expand Up @@ -56,10 +61,22 @@ export abstract class Node<C = unknown> {
hasSupportedProtocol = true

// Healthcheck related params
/**
* Indicates whether a node with alternative IP is available
*/
altIpAvailable = false
/**
* Indicates whether a node with main URL is available
*/
mainUrlAvailable = true
/**
* Indicates whether node is available.
*/
online = true
/**
* Indicates whether prefer a node with alternative IP or not
*/
preferAltIp = false
Comment on lines +64 to +79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need preferAltIp? I think we can compute that value based on altIpAvailable and mainUrlAvailable.

mainUrlAvailable altIpAvailable preferAltIp
undefined undefined false
true undefined false
false undefined true
true true false
true false false
false true true
false false false

undefined is a marker that the node's health hasn't been checked yet.

Copy link
Member Author

@graycraft graycraft Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If current logic works as expected, I think we can optimize something

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this requires to compare 2 properties mainUrlAvailable and altIpAvailable every time for each network instead of 1 preferAltIp

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skranee @S-FrontendDev what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write unit tests for abstract.node.ts and check all the scenarios? @graycraft

/**
* Node ping estimation
*/
Expand Down Expand Up @@ -97,13 +114,16 @@ export abstract class Node<C = unknown> {
healthcheckInProgress = false

constructor(
url: string,
endpoint: NodeInfo,
type: NodeType,
kind: NodeKind,
label: TNodeLabel,
version = '',
minNodeVersion = ''
) {
const { alt_ip, url } = endpoint

this.altIp = alt_ip
this.url = url
this.type = type
this.label = label
Expand Down Expand Up @@ -134,8 +154,21 @@ export abstract class Node<C = unknown> {
this.height = health.height
this.ping = health.ping
this.online = true

if (this.preferAltIp) {
this.altIpAvailable = true
} else {
this.mainUrlAvailable = true
}
} catch {
this.online = false
if (this.preferAltIp) {
this.altIpAvailable = false
this.preferAltIp = false
this.online = false
} else if (this.mainUrlAvailable) {
this.mainUrlAvailable = false
this.preferAltIp = true
}
} finally {
this.healthcheckInProgress = false
}
Expand Down Expand Up @@ -164,6 +197,7 @@ export abstract class Node<C = unknown> {

getStatus() {
return {
alt_ip: this.altIp,
url: this.url,
port: this.port,
hostname: this.hostname,
Expand Down
7 changes: 5 additions & 2 deletions src/lib/nodes/adm/AdmClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
RegisterChatMessageTransaction
} from '@/lib/schema/client'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'
import { AdmNode, Payload, RequestConfig } from './AdmNode'
import { Client } from '../abstract.client'

Expand All @@ -16,7 +17,7 @@ import { Client } from '../abstract.client'
* is not available at the moment.
*/
export class AdmClient extends Client<AdmNode> {
constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') {
constructor(endpoints: NodeInfo[] = [], minNodeVersion = '0.0.0') {
super('adm', 'node', NODE_LABELS.AdmNode)
this.nodes = endpoints.map((endpoint) => new AdmNode(endpoint, minNodeVersion))
this.minNodeVersion = minNodeVersion
Expand Down Expand Up @@ -75,7 +76,9 @@ export class AdmClient extends Client<AdmNode> {
return this.getNode().timeDelta
}

async sendChatTransaction(transaction: RegisterChatMessageTransaction): Promise<CreateNewChatMessageResponseDto> {
async sendChatTransaction(
transaction: RegisterChatMessageTransaction
): Promise<CreateNewChatMessageResponseDto> {
return this.post('/api/chats/process', () => ({ transaction }))
}
}
8 changes: 5 additions & 3 deletions src/lib/nodes/adm/AdmNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GetNodeStatusResponseDto } from '@/lib/schema/client'
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
import { Node } from '@/lib/nodes/abstract.node'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'

type FetchNodeInfoResult = {
socketSupport: boolean
Expand All @@ -18,6 +19,7 @@ export type Payload =
(ctx: AdmNode): Record<string, any>
}
export type RequestConfig<P extends Payload> = {
baseURL?: string
url: string
method?: string
payload?: P
Expand All @@ -28,8 +30,8 @@ export type RequestConfig<P extends Payload> = {
* to the node and verify is status (online/offline, version, ping, etc.)
*/
export class AdmNode extends Node<AxiosInstance> {
constructor(url: string, minNodeVersion = '0.0.0') {
super(url, 'adm', 'node', NODE_LABELS.AdmNode, '', minNodeVersion)
constructor(endpoint: NodeInfo, minNodeVersion = '0.0.0') {
super(endpoint, 'adm', 'node', NODE_LABELS.AdmNode, '', minNodeVersion)

this.wsPort = '36668' // default wsPort
this.wsProtocol = this.protocol === 'https:' ? 'wss:' : 'ws:'
Expand All @@ -52,6 +54,7 @@ export class AdmNode extends Node<AxiosInstance> {
const { url, method = 'get', payload } = cfg

const config: AxiosRequestConfig = {
baseURL: this.preferAltIp ? this.altIp : this.url,
url,
method: method.toLowerCase(),
[method === 'get' ? 'params' : 'data']:
Expand All @@ -72,7 +75,6 @@ export class AdmNode extends Node<AxiosInstance> {
// According to https://github.com/axios/axios#handling-errors this means, that request was sent,
// but server could not respond.
if (!error.response && error.request) {
this.online = false
throw new NodeOfflineError()
}
throw error
Expand Down
5 changes: 3 additions & 2 deletions src/lib/nodes/adm/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import config from '@/config'
import { NodeInfo } from '@/types/wallets'
import type { NodeInfo } from '@/types/wallets'
import { AdmClient } from './AdmClient'

const endpoints = (config.adm.nodes.list as NodeInfo[]).map((endpoint) => endpoint.url)
const endpoints = config.adm.nodes.list as NodeInfo[]

export const adm = new AdmClient(endpoints, config.adm.nodes.minVersion)

export default adm
16 changes: 11 additions & 5 deletions src/lib/nodes/btc-indexer/BtcIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { createBtcLikeClient } from '../utils/createBtcLikeClient'
import { AxiosInstance, AxiosRequestConfig } from 'axios'
import { Node } from '@/lib/nodes/abstract.node'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'

/**
* Encapsulates a node. Provides methods to send API-requests
* to the node and verify is status (online/offline, version, ping, etc.)
*/
export class BtcIndexer extends Node<AxiosInstance> {
constructor(url: string) {
super(url, 'btc', 'service', NODE_LABELS.BtcIndexer)
constructor(endpoint: NodeInfo) {
super(endpoint, 'btc', 'service', NODE_LABELS.BtcIndexer)
}

protected buildClient(): AxiosInstance {
Expand All @@ -18,9 +19,13 @@ export class BtcIndexer extends Node<AxiosInstance> {

protected async checkHealth() {
const time = Date.now()
const blockNumber = await this.client.get('/blocks/tip/height').then((res) => {
return Number(res.data) || 0
})
const blockNumber = await this.client
.get('/blocks/tip/height', {
baseURL: this.preferAltIp ? this.altIp : this.url
})
.then((res) => {
return Number(res.data) || 0
})

return {
height: Number(blockNumber),
Expand All @@ -40,6 +45,7 @@ export class BtcIndexer extends Node<AxiosInstance> {
return this.client
.request({
...requestConfig,
baseURL: this.preferAltIp ? this.altIp : this.url,
url: path,
method,
params: method === 'GET' ? params : undefined,
Expand Down
3 changes: 2 additions & 1 deletion src/lib/nodes/btc-indexer/BtcIndexerClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AxiosRequestConfig } from 'axios'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'
import { Client } from '../abstract.client'
import { BtcIndexer } from './BtcIndexer'
import { MULTIPLIER, normalizeTransaction } from './utils'
Expand All @@ -17,7 +18,7 @@ import { GetUnspentsParams } from './types/api/get-unspents/get-unspents-params'
* is not available at the moment.
*/
export class BtcIndexerClient extends Client<BtcIndexer> {
constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') {
constructor(endpoints: NodeInfo[] = [], minNodeVersion = '0.0.0') {
super('btc', 'service', NODE_LABELS.BtcIndexer)
this.nodes = endpoints.map((endpoint) => new BtcIndexer(endpoint))
this.minNodeVersion = minNodeVersion
Expand Down
5 changes: 3 additions & 2 deletions src/lib/nodes/btc-indexer/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import config from '@/config'
import { NodeInfo } from '@/types/wallets'
import type { NodeInfo } from '@/types/wallets'
import { BtcIndexerClient } from './BtcIndexerClient'

const endpoints = (config.btc.services.btcIndexer.list as NodeInfo[]).map((endpoint) => endpoint.url)
const endpoints = config.btc.services.btcIndexer.list as NodeInfo[]

export const btcIndexer = new BtcIndexerClient(endpoints)

export default btcIndexer
3 changes: 2 additions & 1 deletion src/lib/nodes/btc/BtcClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AxiosRequestConfig } from 'axios'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'
import { BtcNode } from './BtcNode'
import { Client } from '../abstract.client'
import { RpcRequest } from './types/api/common'
Expand All @@ -12,7 +13,7 @@ import { RpcRequest } from './types/api/common'
* is not available at the moment.
*/
export class BtcClient extends Client<BtcNode> {
constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') {
constructor(endpoints: NodeInfo[] = [], minNodeVersion = '0.0.0') {
super('btc', 'node', NODE_LABELS.BtcNode)
this.nodes = endpoints.map((endpoint) => new BtcNode(endpoint))
this.minNodeVersion = minNodeVersion
Expand Down
6 changes: 4 additions & 2 deletions src/lib/nodes/btc/BtcNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createBtcLikeClient } from '../utils/createBtcLikeClient'
import { Node } from '@/lib/nodes/abstract.node'
import { NODE_LABELS } from '@/lib/nodes/constants'
import { formatBtcVersion } from '@/lib/nodes/utils/nodeVersionFormatters'
import type { NodeInfo } from '@/types/wallets'
import { RpcRequest, RpcResponse } from './types/api/common'
import { NetworkInfo } from './types/api/network-info'
import { BlockchainInfo } from './types/api/blockchain-info'
Expand All @@ -12,8 +13,8 @@ import { BlockchainInfo } from './types/api/blockchain-info'
* to the node and verify is status (online/offline, version, ping, etc.)
*/
export class BtcNode extends Node<AxiosInstance> {
constructor(url: string) {
super(url, 'btc', 'node', NODE_LABELS.BtcNode)
constructor(endpoint: NodeInfo) {
super(endpoint, 'btc', 'node', NODE_LABELS.BtcNode)
}

protected buildClient(): AxiosInstance {
Expand Down Expand Up @@ -53,6 +54,7 @@ export class BtcNode extends Node<AxiosInstance> {
return this.client
.request<RpcResponse<Result>>({
...requestConfig,
baseURL: this.preferAltIp ? this.altIp : this.url,
method: 'POST',
data: params
})
Expand Down
5 changes: 3 additions & 2 deletions src/lib/nodes/btc/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import config from '@/config'
import { NodeInfo } from '@/types/wallets'
import type { NodeInfo } from '@/types/wallets'
import { BtcClient } from './BtcClient'

const endpoints = (config.btc.nodes.list as NodeInfo[]).map((endpoint) => endpoint.url)
const endpoints = config.btc.nodes.list as NodeInfo[]

export const btc = new BtcClient(endpoints)

export default btc
1 change: 1 addition & 0 deletions src/lib/nodes/btc/types/api/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Method =
| 'getaddressbalance'

export type RpcRequest<P = any, M extends Method | string = string> = {
baseURL?: string
method: M
params?: P
}
3 changes: 2 additions & 1 deletion src/lib/nodes/dash/DashClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AxiosRequestConfig } from 'axios'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'
import { DashNode } from './DashNode'
import { Client } from '../abstract.client'
import { normalizeTransaction } from './utils'
Expand All @@ -9,7 +10,7 @@ import { Balance } from './types/api/balance'
import { Transaction } from './types/api/transaction'

export class DashClient extends Client<DashNode> {
constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') {
constructor(endpoints: NodeInfo[] = [], minNodeVersion = '0.0.0') {
super('dash', 'node', NODE_LABELS.DashNode)
this.nodes = endpoints.map((endpoint) => new DashNode(endpoint))
this.minNodeVersion = minNodeVersion
Expand Down
7 changes: 5 additions & 2 deletions src/lib/nodes/dash/DashNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AxiosInstance, AxiosRequestConfig } from 'axios'
import { createBtcLikeClient } from '../utils/createBtcLikeClient'
import { Node } from '@/lib/nodes/abstract.node'
import { NODE_LABELS } from '@/lib/nodes/constants'
import type { NodeInfo } from '@/types/wallets'
import { RpcRequest, RpcResponse } from './types/api/common'
import { NetworkInfo } from './types/api/network-info'
import { BlockchainInfo } from './types/api/blockchain-info'
Expand All @@ -11,8 +12,8 @@ import { BlockchainInfo } from './types/api/blockchain-info'
* to the node and verify is status (online/offline, version, ping, etc.)
*/
export class DashNode extends Node<AxiosInstance> {
constructor(url: string) {
super(url, 'dash', 'node', NODE_LABELS.DashNode)
constructor(endpoint: NodeInfo) {
super(endpoint, 'dash', 'node', NODE_LABELS.DashNode)
}

protected buildClient(): AxiosInstance {
Expand Down Expand Up @@ -51,6 +52,7 @@ export class DashNode extends Node<AxiosInstance> {
return this.client
.request<RpcResponse<Result>>({
...requestConfig,
baseURL: this.preferAltIp ? this.altIp : this.url,
url: '/',
method: 'POST',
data: params
Expand All @@ -73,6 +75,7 @@ export class DashNode extends Node<AxiosInstance> {
return this.client
.request<RpcResponse<Result>[]>({
...requestConfig,
baseURL: this.preferAltIp ? this.altIp : this.url,
url: '/',
method: 'POST',
data: params
Expand Down
5 changes: 3 additions & 2 deletions src/lib/nodes/dash/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import config from '@/config'
import { NodeInfo } from '@/types/wallets'
import type { NodeInfo } from '@/types/wallets'
import { DashClient } from './DashClient'

const endpoints = (config.dash.nodes.list as NodeInfo[]).map((endpoint) => endpoint.url)
const endpoints = config.dash.nodes.list as NodeInfo[]

export const dash = new DashClient(endpoints)

export default dash
1 change: 1 addition & 0 deletions src/lib/nodes/dash/types/api/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Method =
| 'getaddressbalance'

export type RpcRequest<P = any, M extends Method | string = string> = {
baseURL?: string
method: M
params?: P
}
Loading