From 79059f62aaebb1aa53d5eeaee86b14991256c81c Mon Sep 17 00:00:00 2001 From: pshenmic Date: Wed, 31 Jul 2024 17:01:03 +0700 Subject: [PATCH] fix(dashmate): status command shows tenderdash error before activation (#2028) --- packages/dashmate/README.md | 20 +++--- .../dashmate/src/commands/status/platform.js | 4 ++ packages/dashmate/src/status/colors.js | 2 + .../dashmate/src/status/determineStatus.js | 7 +- .../src/status/enums/serviceStatus.js | 1 + .../dashmate/src/status/scopes/platform.js | 72 +++++++++++++------ .../test/unit/status/scopes/platform.spec.js | 61 ++++++++++++++-- 7 files changed, 130 insertions(+), 37 deletions(-) diff --git a/packages/dashmate/README.md b/packages/dashmate/README.md index f9ed156cb94..dec9b177136 100644 --- a/packages/dashmate/README.md +++ b/packages/dashmate/README.md @@ -88,7 +88,7 @@ In some cases, you must also additionally reset platform data: ```bash $ dashmate stop $ npm install -g dashmate -$ dashmate reset --platform-only --hard +$ dashmate reset --platform --hard $ dashmate update $ dashmate setup $ dashmate start @@ -283,14 +283,18 @@ The `reset` command removes all data corresponding to the specified config and a ``` USAGE - $ dashmate reset [-v] [--config ] [-h] [-f] [-p] + $ dashmate reset [--config ] [-v] [-h] [-f] [-p] [--keep-data] FLAGS - -f, --force skip running services check - -h, --hard reset config as well as data - -p, --platform-only reset platform data only - -v, --verbose use verbose mode for output - --config= configuration name to use + -f, --force skip running services check + -h, --hard reset config as well as services and data + -p, --platform reset platform services and data only + -v, --verbose use verbose mode for output + --config= configuration name to use + --keep-data keep data + +DESCRIPTION + Reset node data ``` To reset a node: @@ -446,7 +450,7 @@ USAGE FLAGS -f, --force reset even running node - -p, --platform-only reset platform data only + -p, --platform reset platform data only -v, --verbose use verbose mode for output --group= group name to use --hard reset config as well as data diff --git a/packages/dashmate/src/commands/status/platform.js b/packages/dashmate/src/commands/status/platform.js index 073ebd52573..2c0e401f18c 100644 --- a/packages/dashmate/src/commands/status/platform.js +++ b/packages/dashmate/src/commands/status/platform.js @@ -32,6 +32,7 @@ export default class PlatformStatusCommand extends ConfigBaseCommand { flags, dockerCompose, createRpcClient, + getConnectionHost, config, getPlatformScope, ) { @@ -57,6 +58,7 @@ export default class PlatformStatusCommand extends ConfigBaseCommand { if (flags.format === OUTPUT_FORMATS.PLAIN) { const { + platformActivation, httpService, httpPort, httpPortState, @@ -68,6 +70,8 @@ export default class PlatformStatusCommand extends ConfigBaseCommand { drive, } = scope; + plain['Platform Activation'] = platformActivation ? colors.platformActivation(platformActivation)(platformActivation) : 'n/a'; + plain['HTTP service'] = httpService || 'n/a'; plain['HTTP port'] = `${httpPort} ${httpPortState ? colors.portState(httpPortState)(httpPortState) : ''}`; plain['P2P service'] = p2pService || 'n/a'; diff --git a/packages/dashmate/src/status/colors.js b/packages/dashmate/src/status/colors.js index 27c80532b2a..3f30854f377 100644 --- a/packages/dashmate/src/status/colors.js +++ b/packages/dashmate/src/status/colors.js @@ -34,6 +34,7 @@ export default { return chalk.green; case ServiceStatusEnum.syncing: case ServiceStatusEnum.wait_for_core: + case ServiceStatusEnum.wait_for_activation: return chalk.yellow; default: return chalk.red; @@ -73,4 +74,5 @@ export default { } return chalk.red; }, + platformActivation: (string) => (string.startsWith('Activated') ? chalk.green : chalk.yellow), }; diff --git a/packages/dashmate/src/status/determineStatus.js b/packages/dashmate/src/status/determineStatus.js index d31d71cfef1..723302a006f 100644 --- a/packages/dashmate/src/status/determineStatus.js +++ b/packages/dashmate/src/status/determineStatus.js @@ -37,9 +37,14 @@ export default { * Determine platform ServiceStatus based on DockerStatusEnum and core readiness * @param dockerStatus {DockerStatusEnum} * @param coreIsSynced {boolean} + * @param mnRRSoftFork {object} * @returns {ServiceStatusEnum} */ - platform: (dockerStatus, coreIsSynced) => { + platform: (dockerStatus, coreIsSynced, mnRRSoftFork) => { + if (coreIsSynced && !mnRRSoftFork.active) { + return ServiceStatusEnum.wait_for_activation; + } + if (dockerStatus === DockerStatusEnum.running) { return coreIsSynced ? ServiceStatusEnum.up : ServiceStatusEnum.wait_for_core; } diff --git a/packages/dashmate/src/status/enums/serviceStatus.js b/packages/dashmate/src/status/enums/serviceStatus.js index 6a6fc4500d2..09cb1f1c390 100644 --- a/packages/dashmate/src/status/enums/serviceStatus.js +++ b/packages/dashmate/src/status/enums/serviceStatus.js @@ -4,5 +4,6 @@ export const ServiceStatusEnum = { up: 'up', syncing: 'syncing', wait_for_core: 'wait_for_core', + wait_for_activation: 'wait_for_activation', error: 'error', }; diff --git a/packages/dashmate/src/status/scopes/platform.js b/packages/dashmate/src/status/scopes/platform.js index 08241ce60b8..f18b2915498 100644 --- a/packages/dashmate/src/status/scopes/platform.js +++ b/packages/dashmate/src/status/scopes/platform.js @@ -1,3 +1,4 @@ +import prettyMs from 'pretty-ms'; import providers from '../providers.js'; import { DockerStatusEnum } from '../enums/dockerStatus.js'; import { ServiceStatusEnum } from '../enums/serviceStatus.js'; @@ -15,7 +16,7 @@ export default function getPlatformScopeFactory( createRpcClient, getConnectionHost, ) { - async function getMNSync(config) { + async function getCoreInfo(config) { const rpcClient = createRpcClient({ port: config.get('core.rpc.port'), user: 'dashmate', @@ -23,16 +24,27 @@ export default function getPlatformScopeFactory( host: await getConnectionHost(config, 'core', 'core.rpc.host'), }); + const [mnSync, blockchainInfo] = await Promise.all([ + rpcClient.mnsync('status'), + rpcClient.getBlockchainInfo(), + ]); + + const { + result: { + softforks: { mn_rr: mnRR }, + }, + } = blockchainInfo; + const { result: { IsSynced: isSynced, }, - } = await rpcClient.mnsync('status'); + } = mnSync; - return isSynced; + return { isSynced, mnRRSoftFork: mnRR }; } - async function getTenderdashInfo(config, isCoreSynced) { + async function getTenderdashInfo(config, isCoreSynced, mnRRSoftFork) { const info = { p2pPortState: null, httpPortState: null, @@ -63,7 +75,7 @@ export default function getPlatformScopeFactory( } const dockerStatus = await determineStatus.docker(dockerCompose, config, 'drive_tenderdash'); - const serviceStatus = determineStatus.platform(dockerStatus, isCoreSynced); + const serviceStatus = determineStatus.platform(dockerStatus, isCoreSynced, mnRRSoftFork); info.dockerStatus = dockerStatus; info.serviceStatus = serviceStatus; @@ -142,7 +154,7 @@ export default function getPlatformScopeFactory( return info; } - const getDriveInfo = async (config, isCoreSynced) => { + const getDriveInfo = async (config, isCoreSynced, mnRRSoftFork) => { const info = { dockerStatus: null, serviceStatus: null, @@ -150,7 +162,7 @@ export default function getPlatformScopeFactory( try { info.dockerStatus = await determineStatus.docker(dockerCompose, config, 'drive_abci'); - info.serviceStatus = determineStatus.platform(info.dockerStatus, isCoreSynced); + info.serviceStatus = determineStatus.platform(info.dockerStatus, isCoreSynced, mnRRSoftFork); if (info.serviceStatus === ServiceStatusEnum.up) { const driveEchoResult = await dockerCompose.execCommand( @@ -194,6 +206,7 @@ export default function getPlatformScopeFactory( const rpcService = `${rpcHost}:${rpcPort}`; const scope = { + platformActivation: null, coreIsSynced: null, httpPort, httpService, @@ -233,11 +246,14 @@ export default function getPlatformScopeFactory( } } + let coreInfo; + try { - const coreIsSynced = await getMNSync(config); - scope.coreIsSynced = coreIsSynced; + coreInfo = await getCoreInfo(config); - if (!coreIsSynced) { + scope.coreIsSynced = coreInfo.isSynced; + + if (!coreInfo.isSynced) { if (process.env.DEBUG) { // eslint-disable-next-line no-console console.error('Platform status is not available until masternode state is \'READY\''); @@ -250,20 +266,34 @@ export default function getPlatformScopeFactory( } } - const [tenderdash, drive] = await Promise.all([ - getTenderdashInfo(config, scope.coreIsSynced), - getDriveInfo(config, scope.coreIsSynced), - ]); + if (coreInfo) { + const { mnRRSoftFork } = coreInfo; - if (tenderdash) { - scope.tenderdash = tenderdash; + if (mnRRSoftFork.active) { + scope.platformActivation = `Activated (at height ${mnRRSoftFork.height})`; + } else { + const startTime = mnRRSoftFork.bip9.start_time; - scope.httpPortState = tenderdash.httpPortState; - scope.p2pPortState = tenderdash.p2pPortState; - } + const diff = (new Date().getTime() - startTime) / 1000; + + scope.platformActivation = `Waiting for activation (approximately in ${prettyMs(diff, { compact: true })})`; + } + + const [tenderdash, drive] = await Promise.all([ + getTenderdashInfo(config, scope.coreIsSynced, coreInfo.mnRRSoftFork), + getDriveInfo(config, scope.coreIsSynced, coreInfo.mnRRSoftFork), + ]); - if (drive) { - scope.drive = drive; + if (tenderdash) { + scope.tenderdash = tenderdash; + + scope.httpPortState = tenderdash.httpPortState; + scope.p2pPortState = tenderdash.p2pPortState; + } + + if (drive) { + scope.drive = drive; + } } return scope; diff --git a/packages/dashmate/test/unit/status/scopes/platform.spec.js b/packages/dashmate/test/unit/status/scopes/platform.spec.js index ad723f3077e..c3a1c28ff19 100644 --- a/packages/dashmate/test/unit/status/scopes/platform.spec.js +++ b/packages/dashmate/test/unit/status/scopes/platform.spec.js @@ -63,6 +63,13 @@ describe('getPlatformScopeFactory', () => { it('should just work', async () => { mockDetermineDockerStatus.returns(DockerStatusEnum.running); mockRpcClient.mnsync.withArgs('status').returns({ result: { IsSynced: true } }); + mockRpcClient.getBlockchainInfo.returns({ + result: { + softforks: { + mn_rr: { active: true, height: 1337 }, + }, + }, + }); mockDockerCompose.isServiceRunning.returns(true); mockDockerCompose.execCommand.returns({ exitCode: 0, out: '' }); mockMNOWatchProvider.returns(Promise.resolve('OPEN')); @@ -84,6 +91,7 @@ describe('getPlatformScopeFactory', () => { const mockNetInfo = { n_peers: 6, listening: true }; const expectedScope = { + platformActivation: 'Activated (at height 1337)', coreIsSynced: true, httpPort, httpService, @@ -129,6 +137,13 @@ describe('getPlatformScopeFactory', () => { it('should return platform syncing when it is catching up', async () => { mockDetermineDockerStatus.returns(DockerStatusEnum.running); mockRpcClient.mnsync.withArgs('status').returns({ result: { IsSynced: true } }); + mockRpcClient.getBlockchainInfo.returns({ + result: { + softforks: { + mn_rr: { active: true, height: 1337 }, + }, + }, + }); mockDockerCompose.isServiceRunning.returns(true); mockDockerCompose.execCommand.returns({ exitCode: 0, out: '' }); mockMNOWatchProvider.returns(Promise.resolve('OPEN')); @@ -150,6 +165,7 @@ describe('getPlatformScopeFactory', () => { const mockNetInfo = { n_peers: 6, listening: true }; const expectedScope = { + platformActivation: 'Activated (at height 1337)', coreIsSynced: true, httpPort, httpService, @@ -202,6 +218,7 @@ describe('getPlatformScopeFactory', () => { .returns(DockerStatusEnum.running); const expectedScope = { + platformActivation: null, coreIsSynced: null, httpPort, httpService, @@ -213,8 +230,8 @@ describe('getPlatformScopeFactory', () => { tenderdash: { httpPortState: null, p2pPortState: null, - dockerStatus: DockerStatusEnum.running, - serviceStatus: ServiceStatusEnum.wait_for_core, + dockerStatus: null, + serviceStatus: null, version: null, listening: null, catchingUp: null, @@ -227,8 +244,8 @@ describe('getPlatformScopeFactory', () => { network: null, }, drive: { - dockerStatus: DockerStatusEnum.running, - serviceStatus: ServiceStatusEnum.wait_for_core, + dockerStatus: null, + serviceStatus: null, }, }; @@ -238,16 +255,22 @@ describe('getPlatformScopeFactory', () => { }); it('should return empty scope if core is not synced', async () => { - mockDockerCompose.isServiceRunning - .withArgs(config, 'drive_tenderdash') - .returns(true); + mockDockerCompose.isServiceRunning.withArgs(config, 'drive_tenderdash').returns(true); mockDetermineDockerStatus.withArgs(mockDockerCompose, config, 'drive_tenderdash').returns(DockerStatusEnum.running); mockDetermineDockerStatus.withArgs(mockDockerCompose, config, 'drive_abci').returns(DockerStatusEnum.running); mockRpcClient.mnsync.withArgs('status').returns({ result: { IsSynced: false } }); + mockRpcClient.getBlockchainInfo.returns({ + result: { + softforks: { + mn_rr: { active: true, height: 1337 }, + }, + }, + }); mockDockerCompose.execCommand.returns({ exitCode: 1, out: '' }); mockMNOWatchProvider.returns(Promise.resolve('OPEN')); const expectedScope = { + platformActivation: 'Activated (at height 1337)', coreIsSynced: false, httpPort, httpService, @@ -285,6 +308,13 @@ describe('getPlatformScopeFactory', () => { it('should return drive info if tenderdash is failed', async () => { mockRpcClient.mnsync.withArgs('status').returns({ result: { IsSynced: true } }); + mockRpcClient.getBlockchainInfo.returns({ + result: { + softforks: { + mn_rr: { active: true, height: 1337 }, + }, + }, + }); mockDockerCompose.isServiceRunning .withArgs(config, 'drive_tenderdash') .returns(true); @@ -296,6 +326,7 @@ describe('getPlatformScopeFactory', () => { mockMNOWatchProvider.returns(Promise.resolve('OPEN')); const expectedScope = { + platformActivation: 'Activated (at height 1337)', coreIsSynced: true, httpPort, httpService, @@ -333,6 +364,13 @@ describe('getPlatformScopeFactory', () => { it('should still return scope with tenderdash if drive is failed', async () => { mockRpcClient.mnsync.withArgs('status').returns({ result: { IsSynced: true } }); + mockRpcClient.getBlockchainInfo.returns({ + result: { + softforks: { + mn_rr: { active: true, height: 1337 }, + }, + }, + }); mockDockerCompose.isServiceRunning .withArgs(config, 'drive_tenderdash') .returns(true); @@ -366,6 +404,7 @@ describe('getPlatformScopeFactory', () => { .returns(Promise.resolve({ json: () => Promise.resolve(mockNetInfo) })); const expectedScope = { + platformActivation: 'Activated (at height 1337)', coreIsSynced: true, httpPort, httpService, @@ -403,6 +442,13 @@ describe('getPlatformScopeFactory', () => { it('should have error service status in case FetchError to tenderdash', async () => { mockRpcClient.mnsync.returns({ result: { IsSynced: true } }); + mockRpcClient.getBlockchainInfo.returns({ + result: { + softforks: { + mn_rr: { active: true, height: 1337 }, + }, + }, + }); mockDockerCompose.isServiceRunning .withArgs(config, 'drive_tenderdash') .returns(true); @@ -412,6 +458,7 @@ describe('getPlatformScopeFactory', () => { mockFetch.returns(Promise.reject(new Error('FetchError'))); const expectedScope = { + platformActivation: 'Activated (at height 1337)', coreIsSynced: true, httpPort, httpService,