diff --git a/src/components/nodes/components/NodeVersion.vue b/src/components/nodes/components/NodeVersion.vue index 128ef1451..f60f04240 100644 --- a/src/components/nodes/components/NodeVersion.vue +++ b/src/components/nodes/components/NodeVersion.vue @@ -1,6 +1,6 @@ @@ -21,7 +21,7 @@ export default defineComponent({ } }, setup(props) { - const version = computed(() => props.node.version) + const version = computed(() => props.node.displayVersion) return { version, diff --git a/src/lib/nodes/abstract.node.ts b/src/lib/nodes/abstract.node.ts index 94401f836..5451cfdb0 100644 --- a/src/lib/nodes/abstract.node.ts +++ b/src/lib/nodes/abstract.node.ts @@ -98,7 +98,7 @@ export abstract class Node { timer?: NodeJS.Timeout healthCheckInterval: HealthcheckInterval = 'normal' - abstract client: C + client: C constructor( url: string, @@ -119,6 +119,14 @@ export abstract class Node { this.version = version this.hasSupportedProtocol = !(this.protocol === 'http:' && appProtocol === 'https:') this.active = nodesStorage.isActive(url) + + this.client = this.buildClient() + + if (this.active) { + void this.fetchNodeVersion() + } + + void this.startHealthcheck() } async startHealthcheck() { @@ -176,6 +184,7 @@ export abstract class Node { active: this.active, outOfSync: this.outOfSync, hasMinNodeVersion: this.hasMinNodeVersion(), + displayVersion: this.displayVersion(), hasSupportedProtocol: this.hasSupportedProtocol, socketSupport: this.socketSupport, height: this.height, @@ -209,6 +218,7 @@ export abstract class Node { } protected abstract checkHealth(): Promise + protected abstract buildClient(): C /** * Enables/disables a node. @@ -220,6 +230,16 @@ export abstract class Node { return this.getStatus() } + + displayVersion() { + return this.version ? `v${this.version}` : '' + } + + // This method is not abstract because Not all nodes need version checking (For example: indexers) or some nodes receive version information from healthcheck requests. + // Therefore, it is overridden only in the case when a separate request is needed to obtain the version. + protected fetchNodeVersion() { + return Promise.resolve() + } } export type NodeStatusResult = ReturnType diff --git a/src/lib/nodes/adm/AdmNode.ts b/src/lib/nodes/adm/AdmNode.ts index 70d1f8c0e..fe0563169 100644 --- a/src/lib/nodes/adm/AdmNode.ts +++ b/src/lib/nodes/adm/AdmNode.ts @@ -27,25 +27,19 @@ export type RequestConfig

= { * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class AdmNode extends Node { - client: AxiosInstance - +export class AdmNode extends Node { constructor(url: string, minNodeVersion = '0.0.0') { - super(url, 'adm', 'node', NODE_LABELS.AdmNode, minNodeVersion, minNodeVersion) + super(url, 'adm', 'node', NODE_LABELS.AdmNode, '', minNodeVersion) this.wsPort = '36668' // default wsPort this.wsProtocol = this.protocol === 'https:' ? 'wss:' : 'ws:' this.wsPortNeeded = this.wsProtocol === 'ws:' && !this.hostname.includes('.onion') + } - this.client = axios.create({ + protected buildClient(): AxiosInstance { + return axios.create({ baseURL: this.url }) - - // Don't fetch node info if user disabled it - if (this.active) { - void this.fetchNodeInfo() - } - void this.startHealthcheck() } /** @@ -91,21 +85,24 @@ export class AdmNode extends Node { * @returns {Promise<{version: string, height: number, ping: number}>} */ private async fetchNodeInfo(): Promise { - const response: GetNodeStatusResponseDto = await this.request({ url: '/api/node/status' }) - - if (response.success) { - const version = response.version.version - const height = Number(response.network.height) - const socketSupport = response.wsClient ? response.wsClient.enabled : false - const wsPort = response.wsClient ? String(response.wsClient.port) : '' - - this.version = version + const { success, version, network, wsClient } = await this.request< + Payload, + GetNodeStatusResponseDto + >({ url: '/api/node/status' }) + + if (success) { + const readableVersion = version.version + const height = Number(network.height) + const socketSupport = wsClient ? wsClient.enabled : false + const wsPort = wsClient ? String(wsClient.port) : '' + + this.version = readableVersion this.height = height this.socketSupport = socketSupport this.wsPort = wsPort return { - version, + version: readableVersion, height, socketSupport, wsPort @@ -117,10 +114,10 @@ export class AdmNode extends Node { protected async checkHealth() { const time = Date.now() - const nodeInfo = await this.fetchNodeInfo() + const { height } = await this.fetchNodeInfo() return { - height: nodeInfo.height, + height, ping: Date.now() - time } } diff --git a/src/lib/nodes/btc/BtcNode.ts b/src/lib/nodes/btc/BtcNode.ts index c4d0b5063..4d18e4186 100644 --- a/src/lib/nodes/btc/BtcNode.ts +++ b/src/lib/nodes/btc/BtcNode.ts @@ -2,20 +2,26 @@ import { createBtcLikeClient } from '../utils/createBtcLikeClient' import type { AxiosInstance } from 'axios' import { Node } from '@/lib/nodes/abstract.node' import { NODE_LABELS } from '@/lib/nodes/constants' +import { formatBtcVersion } from '@/lib/nodes/utils/nodeVersionFormatters.ts' + +type FetchBtcNodeInfoResult = { + error: string + result: { + version: number + } +} /** * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class BtcNode extends Node { - client: AxiosInstance - +export class BtcNode extends Node { constructor(url: string) { super(url, 'btc', 'node', NODE_LABELS.BtcNode) + } - this.client = createBtcLikeClient(url) - - void this.startHealthcheck() + protected buildClient(): AxiosInstance { + return createBtcLikeClient(this.url) } protected async checkHealth() { @@ -29,4 +35,17 @@ export class BtcNode extends Node { ping: Date.now() - time } } + + protected async fetchNodeVersion(): Promise { + const { data } = await this.client.post('/bitcoind', { + jsonrpc: '1.0', + id: 'adm', + method: 'getnetworkinfo', + params: [] + }) + const { version } = data.result + if (version) { + this.version = formatBtcVersion(version) + } + } } diff --git a/src/lib/nodes/dash/DashNode.ts b/src/lib/nodes/dash/DashNode.ts index 9e3082c26..5170c194f 100644 --- a/src/lib/nodes/dash/DashNode.ts +++ b/src/lib/nodes/dash/DashNode.ts @@ -3,19 +3,24 @@ import type { AxiosInstance } from 'axios' import { Node } from '@/lib/nodes/abstract.node' import { NODE_LABELS } from '@/lib/nodes/constants' +type FetchNodeVersionResponse = { + error: string + result: { + buildversion: string + } +} + /** * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class DashNode extends Node { - client: AxiosInstance - +export class DashNode extends Node { constructor(url: string) { super(url, 'dash', 'node', NODE_LABELS.DashNode) + } - this.client = createBtcLikeClient(url) - - void this.startHealthcheck() + protected buildClient(): AxiosInstance { + return createBtcLikeClient(this.url) } protected async checkHealth() { @@ -31,4 +36,14 @@ export class DashNode extends Node { ping: Date.now() - time } } + + protected async fetchNodeVersion(): Promise { + const { data } = await this.client.post('/', { + method: 'getnetworkinfo' + }) + const { buildversion } = data.result + if (buildversion) { + this.version = buildversion.replace('v', '') + } + } } diff --git a/src/lib/nodes/doge/DogeNode.ts b/src/lib/nodes/doge/DogeNode.ts index d010cc6ab..d11443584 100644 --- a/src/lib/nodes/doge/DogeNode.ts +++ b/src/lib/nodes/doge/DogeNode.ts @@ -2,20 +2,25 @@ import { createBtcLikeClient } from '../utils/createBtcLikeClient' import type { AxiosInstance } from 'axios' import { Node } from '@/lib/nodes/abstract.node' import { NODE_LABELS } from '@/lib/nodes/constants' +import { formatDogeVersion } from '@/lib/nodes/utils/nodeVersionFormatters.ts' + +type FetchBtcNodeInfoResult = { + info: { + version: number + } +} /** * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class DogeNode extends Node { - client: AxiosInstance - +export class DogeNode extends Node { constructor(url: string) { super(url, 'doge', 'node', NODE_LABELS.DogeNode) + } - this.client = createBtcLikeClient(url) - - void this.startHealthcheck() + protected buildClient(): AxiosInstance { + return createBtcLikeClient(this.url) } protected async checkHealth() { @@ -29,4 +34,12 @@ export class DogeNode extends Node { ping: Date.now() - time } } + + protected async fetchNodeVersion(): Promise { + const { data } = await this.client.get('/api/status') + const { version } = data.info + if (version) { + this.version = formatDogeVersion(version) + } + } } diff --git a/src/lib/nodes/eth-indexer/EthIndexer.ts b/src/lib/nodes/eth-indexer/EthIndexer.ts index 7bf6c6a92..f05d51ccc 100644 --- a/src/lib/nodes/eth-indexer/EthIndexer.ts +++ b/src/lib/nodes/eth-indexer/EthIndexer.ts @@ -7,19 +7,13 @@ import { NODE_LABELS } from '@/lib/nodes/constants' * ETH Indexer API * https://github.com/Adamant-im/ETH-transactions-storage */ -export class EthIndexer extends Node { - client: AxiosInstance - +export class EthIndexer extends Node { constructor(url: string) { super(url, 'eth', 'service', NODE_LABELS.EthIndexer) + } - this.client = axios.create({ baseURL: url }) - - // Don't fetch node info if user disabled it - if (this.active) { - void this.fetchServiceInfo() - } - void this.startHealthcheck() + protected buildClient(): AxiosInstance { + return axios.create({ baseURL: this.url }) } /** diff --git a/src/lib/nodes/eth/EthNode.ts b/src/lib/nodes/eth/EthNode.ts index eb61e9fed..2ebc5020e 100644 --- a/src/lib/nodes/eth/EthNode.ts +++ b/src/lib/nodes/eth/EthNode.ts @@ -2,25 +2,21 @@ import Web3Eth from 'web3-eth' import { HttpProvider } from 'web3-providers-http' import { Node } from '@/lib/nodes/abstract.node' import { NODE_LABELS } from '@/lib/nodes/constants' +import { formatEthVersion } from '@/lib/nodes/utils/nodeVersionFormatters.ts' /** * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class EthNode extends Node { - /** - * @custom - */ - provider: HttpProvider - client: Web3Eth +export class EthNode extends Node { + clientName = '' constructor(url: string) { super(url, 'eth', 'node', NODE_LABELS.EthNode) + } - this.provider = new HttpProvider(this.url) - this.client = new Web3Eth(this.provider) - - void this.startHealthcheck() + protected buildClient(): Web3Eth { + return new Web3Eth(new HttpProvider(this.url)) } protected async checkHealth() { @@ -32,4 +28,14 @@ export class EthNode extends Node { ping: Date.now() - time } } + + protected async fetchNodeVersion(): Promise { + const { clientName, simplifiedVersion } = formatEthVersion(await this.client.getNodeInfo()) + this.version = simplifiedVersion + this.clientName = clientName + } + + displayVersion(): string { + return this.clientName && this.version ? `${this.clientName}/${this.version}` : '' + } } diff --git a/src/lib/nodes/lsk-indexer/LskIndexer.ts b/src/lib/nodes/lsk-indexer/LskIndexer.ts index b4965314a..d5ca66004 100644 --- a/src/lib/nodes/lsk-indexer/LskIndexer.ts +++ b/src/lib/nodes/lsk-indexer/LskIndexer.ts @@ -7,19 +7,13 @@ import { Endpoints } from './types/api/endpoints' * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class LskIndexer extends Node { - client: AxiosInstance - +export class LskIndexer extends Node { constructor(url: string) { super(url, 'lsk', 'service', NODE_LABELS.LskIndexer) + } - this.client = axios.create({ baseURL: `${url}/api/v3` }) - - // Don't fetch node info if user disabled it - if (this.active) { - void this.fetchServiceInfo() - } - void this.startHealthcheck() + protected buildClient(): AxiosInstance { + return axios.create({ baseURL: `${this.url}/api/v3` }) } /** diff --git a/src/lib/nodes/lsk/LskNode.ts b/src/lib/nodes/lsk/LskNode.ts index a2085227e..e02a18e30 100644 --- a/src/lib/nodes/lsk/LskNode.ts +++ b/src/lib/nodes/lsk/LskNode.ts @@ -9,19 +9,13 @@ import { v4 as uuid } from 'uuid' * Encapsulates a node. Provides methods to send API-requests * to the node and verify is status (online/offline, version, ping, etc.) */ -export class LskNode extends Node { - client: AxiosInstance - +export class LskNode extends Node { constructor(url: string) { super(url, 'lsk', 'node', NODE_LABELS.LskNode) + } - this.client = axios.create({ baseURL: url }) - - // Don't fetch node info if user disabled it - if (this.active) { - void this.fetchNodeInfo() - } - void this.startHealthcheck() + protected buildClient(): AxiosInstance { + return axios.create({ baseURL: this.url }) } /** @@ -51,20 +45,10 @@ export class LskNode extends Node { }) } - private async fetchNodeInfo(): Promise<{ version: string; height: number }> { - const { version, height } = await this.invoke('system_getNodeInfo') - this.version = version - this.height = height - - return { - version, - height - } - } - protected async checkHealth() { const time = Date.now() - const { height } = await this.invoke('system_getNodeInfo') + const { height, version } = await this.invoke('system_getNodeInfo') + this.version = version return { height, diff --git a/src/lib/nodes/utils/nodeVersionFormatters.ts b/src/lib/nodes/utils/nodeVersionFormatters.ts new file mode 100644 index 000000000..a116b257f --- /dev/null +++ b/src/lib/nodes/utils/nodeVersionFormatters.ts @@ -0,0 +1,27 @@ +export const formatBtcVersion = (version: number) => { + const [major, minor, patch] = getBtcVersionParts(version) + if (major === 0) { + return `${major}.${minor}.${patch}` + } else { + return `${major}.${minor}` + } +} + +export const formatDogeVersion = (version: number) => { + const [major, minor, patch] = getBtcVersionParts(version) + return `${major}.${minor}.${patch}` +} + +export const formatEthVersion = (version: string) => { + const parts = version.split('/') + const [clientName = '', fullVersion = ''] = parts + const [simplifiedVersion = ''] = fullVersion.split('-') + return { clientName, simplifiedVersion } +} + +export const getBtcVersionParts = (version: number) => + version + .toString() + .padStart(8, '0') + .match(/.{1,2}/g) + ?.map(Number) || []