From c7889f7fbfcee3fdc942280321b78f11a04a6ed5 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Wed, 16 Aug 2023 17:02:45 +0800 Subject: [PATCH] feat: Add check ckb node update or compatibility --- compatible.csv | 5 + .../neuron-ui/src/containers/Main/index.tsx | 1 + .../neuron-ui/src/containers/Navbar/index.tsx | 58 +++++- packages/neuron-ui/src/locales/en.json | 5 +- packages/neuron-ui/src/locales/zh-tw.json | 5 +- packages/neuron-ui/src/locales/zh.json | 5 +- packages/neuron-ui/src/services/remote/app.ts | 16 ++ .../src/services/remote/remoteApiWrapper.ts | 1 + packages/neuron-ui/src/types/App/index.d.ts | 1 + .../neuron-ui/src/types/Subject/index.d.ts | 1 + .../src/widgets/AlertDialog/index.tsx | 22 ++- packages/neuron-wallet/electron-builder.yml | 2 +- packages/neuron-wallet/package.json | 5 + packages/neuron-wallet/src/controllers/api.ts | 7 + packages/neuron-wallet/src/locales/en.ts | 6 - packages/neuron-wallet/src/locales/zh-tw.ts | 6 - packages/neuron-wallet/src/locales/zh.ts | 6 - .../src/models/subjects/show-global-dialog.ts | 1 + packages/neuron-wallet/src/services/node.ts | 84 ++++++--- .../neuron-wallet/tests/services/node.test.ts | 171 ++++++++++++------ 20 files changed, 294 insertions(+), 114 deletions(-) create mode 100644 compatible.csv diff --git a/compatible.csv b/compatible.csv new file mode 100644 index 0000000000..eb63e56fe8 --- /dev/null +++ b/compatible.csv @@ -0,0 +1,5 @@ +CKB,0.110,0.109,0.108,0.107,0.106,0.105,0.104,0.103 +Neuron +0.110,yes,yes,no,no,no,no,no,no +0.106,no,no,yes,yes,yes,yes,no,no +0.103,no,no,no,no,no,no,yes,yes diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx index 03d52d0d43..0f2175d155 100644 --- a/packages/neuron-ui/src/containers/Main/index.tsx +++ b/packages/neuron-ui/src/containers/Main/index.tsx @@ -57,6 +57,7 @@ const MainContent = () => { show={!!globalAlertDialog} title={globalAlertDialog?.title} message={globalAlertDialog?.message} + action={globalAlertDialog?.action} type={globalAlertDialog?.type ?? 'success'} onCancel={onCancelGlobalDialog} /> diff --git a/packages/neuron-ui/src/containers/Navbar/index.tsx b/packages/neuron-ui/src/containers/Navbar/index.tsx index a784f781c6..0d3efd2c91 100644 --- a/packages/neuron-ui/src/containers/Navbar/index.tsx +++ b/packages/neuron-ui/src/containers/Navbar/index.tsx @@ -2,8 +2,14 @@ import React, { useCallback, useEffect, useState } from 'react' import { createPortal } from 'react-dom' import { useLocation, NavLink, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { NeuronWalletActions, useDispatch, useState as useGlobalState } from 'states' -import { checkForUpdates } from 'services/remote' +import { NeuronWalletActions, showGlobalAlertDialog, useDispatch, useState as useGlobalState } from 'states' +import { + VerifyCkbVersionResult, + VerifyExternalCkbNodeRes, + checkForUpdates, + getVersion, + verifyExternalCkbNode, +} from 'services/remote' import { AppUpdater as AppUpdaterSubject } from 'services/subjects' import Badge from 'widgets/Badge' import Logo from 'widgets/Icons/Logo.png' @@ -18,7 +24,7 @@ import { ArrowOpenRight, MenuExpand, } from 'widgets/Icons/icon' -import { RoutePath, clsx, useOnLocaleChange } from 'utils' +import { RoutePath, clsx, isSuccessResponse, useOnLocaleChange } from 'utils' import Tooltip from 'widgets/Tooltip' import styles from './navbar.module.scss' @@ -105,6 +111,52 @@ const Navbar = () => { } }, []) + const [verifyCkbResult, setVerifyCkbResult] = useState() + + useEffect(() => { + verifyExternalCkbNode().then(res => { + if (isSuccessResponse(res) && res.result) { + setVerifyCkbResult(res.result) + } + }) + }, []) + + useEffect(() => { + if (!verifyCkbResult) { + return + } + switch (verifyCkbResult.ckb) { + case VerifyCkbVersionResult.Same: + case VerifyCkbVersionResult.Compatible: + if (!verifyCkbResult.withIndexer) { + showGlobalAlertDialog({ + type: 'warning', + message: t('navbar.ckb-without-indexer'), + action: 'ok', + })(dispatch) + } + break + case VerifyCkbVersionResult.ShouldUpdate: + if (version) { + showGlobalAlertDialog({ + type: 'warning', + message: t('navbar.update-neuron-with-ckb', { version: getVersion() }), + action: 'ok', + })(dispatch) + } + break + case VerifyCkbVersionResult.Incompatible: + showGlobalAlertDialog({ + type: 'warning', + message: t('navbar.ckb-node-compatible', { version: getVersion() }), + action: 'ok', + })(dispatch) + break + default: + break + } + }, [verifyCkbResult, version]) + useEffect(() => { if (pathname.includes(RoutePath.Settings)) { setIsClickedSetting(true) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index d26f07816d..ac19c04625 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -22,7 +22,10 @@ "sync-not-start": "Sync not started yet", "connecting": "Connecting", "experimental-functions": "Experimental", - "s-udt": "Asset Accounts" + "s-udt": "Asset Accounts", + "update-neuron-with-ckb": "The version of the CKB node does not match Neuron (v{{ version }}), which may cause compatibility issues. Please update to the latest version of Neuron.", + "ckb-node-compatible": "CKB node is not compatible with Neuron (v{{ version }}), please check before further operation.", + "ckb-without-indexer": "Please add '--indexer' option to start local node" }, "network-status": { "tooltip": { diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 57491b8bab..fbb50dd194 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -22,7 +22,10 @@ "sync-not-start": "同步尚未開始", "connecting": "正在連接節點", "experimental-functions": "實驗性功能", - "s-udt": "資產賬戶" + "s-udt": "資產賬戶", + "update-neuron-with-ckb": "CKB 節點版本與 Neuron (v{{ version }}) 不匹配,可能導致兼容性問題。請更新到最新版 Neuron。", + "ckb-node-compatible": "CKB 節點版本與 Neuron (v{{ version }}) 不兼容,請謹慎使用。", + "ckb-without-indexer": "請添加 '--indexer' 參數來啟動本地節點" }, "network-status": { "tooltip": { diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index f09bb86c42..27cb6525dc 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -22,7 +22,10 @@ "sync-not-start": "同步尚未开始", "connecting": "正在连接节点", "experimental-functions": "实验性功能", - "s-udt": "资产账户" + "s-udt": "资产账户", + "update-neuron-with-ckb": "CKB 节点版本与 Neuron (v{{ version }}) 不匹配,可能导致兼容性问题。请更新到最新版 Neuron。", + "ckb-node-compatible": "CKB 节点版本与 Neuron (v{{ version }}) 不兼容,请谨慎使用。", + "ckb-without-indexer": "请添加 '--indexer' 参数来启动本地节点" }, "network-status": { "tooltip": { diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts index 167c4893fa..813c6c6304 100644 --- a/packages/neuron-ui/src/services/remote/app.ts +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -24,6 +24,22 @@ export const stopProcessMonitor = remoteApi<'ckb'>('stop-process-monitor') export const startProcessMonitor = remoteApi<'ckb'>('start-process-monitor') export const getIsCkbRunExternal = remoteApi('is-ckb-run-external') +export enum VerifyCkbVersionResult { + Same, + Compatible, + ShouldUpdate, + Incompatible, +} + +export type VerifyExternalCkbNodeRes = + | { + ckb: VerifyCkbVersionResult + withIndexer: boolean + } + | undefined + +export const verifyExternalCkbNode = remoteApi('verify-external-ckb-node') + export const clearCellCache = remoteApi('clear-cache') export const invokeShowErrorMessage = remoteApi<{ title: string; content: string }>('show-error-message') diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index ff31d10f1e..d8f798b382 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -49,6 +49,7 @@ type Action = | 'is-dark' | 'set-theme' | 'is-ckb-run-external' + | 'verify-external-ckb-node' // Wallets | 'get-all-wallets' | 'get-current-wallet' diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 99d9f47c26..dfa736f910 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -139,6 +139,7 @@ declare namespace State { title?: string message?: string type: 'success' | 'failed' | 'warning' + action?: 'ok' | 'cancel' | 'all' onClose?: () => void onOk?: () => void onCancel?: () => void diff --git a/packages/neuron-ui/src/types/Subject/index.d.ts b/packages/neuron-ui/src/types/Subject/index.d.ts index 3a4c4ed450..fcba2123dd 100644 --- a/packages/neuron-ui/src/types/Subject/index.d.ts +++ b/packages/neuron-ui/src/types/Subject/index.d.ts @@ -67,5 +67,6 @@ declare namespace Subject { title?: string message?: string type: 'success' | 'failed' | 'warning' + action?: 'ok' | 'cancel' } } diff --git a/packages/neuron-ui/src/widgets/AlertDialog/index.tsx b/packages/neuron-ui/src/widgets/AlertDialog/index.tsx index 1f5d459e00..747d75d11f 100644 --- a/packages/neuron-ui/src/widgets/AlertDialog/index.tsx +++ b/packages/neuron-ui/src/widgets/AlertDialog/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react' +import React, { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useDialog } from 'utils' import Button from 'widgets/Button' @@ -8,6 +8,7 @@ import Tips from 'widgets/Icons/Tips.png' import styles from './alertDialog.module.scss' type AlertType = 'success' | 'failed' | 'warning' +type Action = 'ok' | 'cancel' | 'all' const AlertDialog = ({ show, @@ -17,6 +18,7 @@ const AlertDialog = ({ onClose, onOk, onCancel, + action, }: { show?: boolean title?: string @@ -25,10 +27,17 @@ const AlertDialog = ({ onClose?: () => void onOk?: () => void onCancel?: () => void + action?: Action }) => { const [t] = useTranslation() const dialogRef = useRef(null) useDialog({ show, dialogRef, onClose: onClose || (() => {}) }) + const actions = useMemo<('cancel' | 'ok')[]>(() => { + if (action) { + return action === 'all' ? ['cancel', 'ok'] : [action] + } + return type === 'warning' ? ['cancel', 'ok'] : ['ok'] + }, [action, type]) return ( @@ -38,13 +47,12 @@ const AlertDialog = ({

{title}

{message}

- {type === 'failed' &&
diff --git a/packages/neuron-wallet/electron-builder.yml b/packages/neuron-wallet/electron-builder.yml index 6cc88df861..6318166a0c 100644 --- a/packages/neuron-wallet/electron-builder.yml +++ b/packages/neuron-wallet/electron-builder.yml @@ -13,7 +13,7 @@ afterSign: scripts/notarize.js files: - from: "../.." to: "." - filter: ["!**/*", ".ckb-version", ".ckb-light-version", "ormconfig.json"] + filter: ["!**/*", ".ckb-version", ".ckb-light-version", "ormconfig.json", "compatible.csv"] - package.json - dist - ".env" diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 5a95b76015..400aa53405 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -34,6 +34,11 @@ "prettier --ignore-path ../../.prettierignore --write", "eslint --fix", "git add" + ], + "test/**/*.{js,cjs,mjs,jsx,ts,tsx}": [ + "prettier --ignore-path ../../.prettierignore --write", + "eslint --fix", + "git add" ] }, "dependencies": { diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 30f8514ba1..6d6f56208a 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -296,6 +296,13 @@ export default class ApiController { } }) + handle('verify-external-ckb-node', async () => { + return { + status: ResponseCode.Success, + result: await NodeService.getInstance().verifyExternalCkbNode(), + } + }) + // Wallets handle('get-all-wallets', async () => { diff --git a/packages/neuron-wallet/src/locales/en.ts b/packages/neuron-wallet/src/locales/en.ts index a7ec296857..2b919a62b2 100644 --- a/packages/neuron-wallet/src/locales/en.ts +++ b/packages/neuron-wallet/src/locales/en.ts @@ -218,12 +218,6 @@ export default { cancel: 'Cancel', }, }, - 'node-version-different': { - message: 'The node version is inconsistent with Neuron(v {{ version }}), please use after confirmation', - }, - 'ckb-without-indexer': { - message: "Please add '--indexer' option to start local node", - }, }, prompt: { password: { diff --git a/packages/neuron-wallet/src/locales/zh-tw.ts b/packages/neuron-wallet/src/locales/zh-tw.ts index 4a7ee1e5b5..1c7d1d4ff1 100644 --- a/packages/neuron-wallet/src/locales/zh-tw.ts +++ b/packages/neuron-wallet/src/locales/zh-tw.ts @@ -205,12 +205,6 @@ export default { cancel: '取消', }, }, - 'node-version-different': { - message: '節點版本與 Neuron(v {{ version }}) 不壹致,請確認後使用', - }, - 'ckb-without-indexer': { - message: "請添加 '--indexer' 參數來啟動本地節點", - }, }, prompt: { password: { diff --git a/packages/neuron-wallet/src/locales/zh.ts b/packages/neuron-wallet/src/locales/zh.ts index e1f0dc5a37..b1a62625c2 100644 --- a/packages/neuron-wallet/src/locales/zh.ts +++ b/packages/neuron-wallet/src/locales/zh.ts @@ -206,12 +206,6 @@ export default { cancel: '取消', }, }, - 'node-version-different': { - message: '节点版本与 Neuron(v {{ version }}) 不一致,请确认后使用', - }, - 'ckb-without-indexer': { - message: "请添加 '--indexer' 参数来启动本地节点", - }, }, prompt: { password: { diff --git a/packages/neuron-wallet/src/models/subjects/show-global-dialog.ts b/packages/neuron-wallet/src/models/subjects/show-global-dialog.ts index 1c71462098..2ed7b79a55 100644 --- a/packages/neuron-wallet/src/models/subjects/show-global-dialog.ts +++ b/packages/neuron-wallet/src/models/subjects/show-global-dialog.ts @@ -4,6 +4,7 @@ const ShowGlobalDialogSubject = new Subject<{ title?: string message?: string type: 'success' | 'failed' | 'warning' + action?: 'ok' | 'cancel' }>() export default ShowGlobalDialogSubject diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts index c998d5331b..9362b60f61 100644 --- a/packages/neuron-wallet/src/services/node.ts +++ b/packages/neuron-wallet/src/services/node.ts @@ -21,6 +21,13 @@ import { generateRPC } from '../utils/ckb-rpc' import startMonitor from './monitor' import { CKBLightRunner } from './light-runner' +export enum VerifyCkbVersionResult { + Same, + Compatible, + ShouldUpdate, + Incompatible, +} + class NodeService { private static instance: NodeService @@ -137,16 +144,25 @@ class NodeService { const isDefaultCKBNeedStart = await this.isDefaultCKBNeedRestart() if (isDefaultCKBNeedStart) { logger.info('CKB:\texternal RPC on default uri not detected, starting bundled CKB node.') + this._isCkbNodeExternal = false const redistReady = await redistCheck() await (redistReady ? this.startNode() : this.showGuideDialog()) await startMonitor() } else { logger.info('CKB:\texternal RPC on default uri detected, skip starting bundled CKB node.') this._isCkbNodeExternal = true - const network = NetworksService.getInstance().getCurrent() - if (network.type !== NetworkType.Light) { - await this.verifyNodeVersion() - await this.verifyStartWithIndexer() + } + } + + public async verifyExternalCkbNode() { + const network = NetworksService.getInstance().getCurrent() + if (this._isCkbNodeExternal && network.type !== NetworkType.Light) { + const localNodeInfo = await new RpcService(network.remote).localNodeInfo() + const internalNodeVersion = this.getInternalNodeVersion() + if (!internalNodeVersion || !localNodeInfo.version) return + return { + ckb: this.getCompatibility(internalNodeVersion, localNodeInfo.version), + withIndexer: await this.isStartWithIndexer(), } } } @@ -219,38 +235,58 @@ class NodeService { } } - private async verifyNodeVersion() { - const network = NetworksService.getInstance().getCurrent() - const localNodeInfo = await new RpcService(network.remote).localNodeInfo() - const internalNodeVersion = this.getInternalNodeVersion() - const [internalMajor, internalMinor] = internalNodeVersion?.split('.') ?? [] - const [externalMajor, externalMinor] = localNodeInfo.version?.split('.') ?? [] + private getNeuronCompatibilityCKB(neuronVersion: string) { + const appPath = electronApp.isPackaged ? electronApp.getAppPath() : path.join(__dirname, '../../../..') + const compatiblePath = path.join(appPath, 'compatible.csv') + if (fs.existsSync(compatiblePath)) { + try { + const neuronCompatibleVersion = neuronVersion.split('.').slice(0, 2).join('.') + const content = fs.readFileSync(compatiblePath, 'utf8')?.split('\n') + const ckbVersions = content?.[0]?.split(',')?.slice(1) + const neuronCompatible = content + .find(v => v.startsWith(neuronCompatibleVersion)) + ?.split(',') + ?.slice(1) + if (neuronCompatible?.length && ckbVersions?.length) { + return neuronCompatible + .map((v: 'yes' | 'no', idx) => { + if (v === 'yes') return ckbVersions[idx] + }) + .filter((v): v is string => !!v) + } + } catch (err) { + logger.error('App\t: get compatible table failed') + } + } + } - if (internalMajor !== externalMajor || (externalMajor === '0' && internalMinor !== externalMinor)) { - dialog.showMessageBox({ - type: 'warning', - message: t('messageBox.node-version-different.message', { version: internalNodeVersion }), - }) + private getCompatibility(neuronCKBVersion: string, externalCKBVersion: string) { + const [internalMajor, internalMinor] = neuronCKBVersion.split('.')?.map(v => +v) ?? [] + const [externalMajor, externalMinor] = externalCKBVersion.split('.')?.map(v => +v) ?? [] + + if (internalMajor === externalMajor && internalMinor === externalMinor) return VerifyCkbVersionResult.Same + if (internalMajor < externalMajor || (internalMajor === externalMajor && internalMinor < externalMinor)) { + return VerifyCkbVersionResult.ShouldUpdate } + const supportCkbVersions = this.getNeuronCompatibilityCKB(neuronCKBVersion) + if (supportCkbVersions?.every(v => !externalCKBVersion.startsWith(v))) { + return VerifyCkbVersionResult.Incompatible + } + return VerifyCkbVersionResult.Compatible } - private async verifyStartWithIndexer() { + private async isStartWithIndexer() { const network = NetworksService.getInstance().getCurrent() try { const res = await rpcRequest<{ error?: { code: number } }>(network.remote, { method: 'get_indexer_tip' }) if (res.error?.code === START_WITHOUT_INDEXER) { logger.info('Node:\tthe ckb node does not start with --indexer') - dialog.showMessageBox({ - type: 'warning', - message: t('messageBox.ckb-without-indexer.message'), - }) + return false } + return true } catch (error) { logger.info('Node:\tcalling get_indexer_tip failed') - dialog.showMessageBox({ - type: 'warning', - message: t('messageBox.ckb-without-indexer.message'), - }) + return false } } } diff --git a/packages/neuron-wallet/tests/services/node.test.ts b/packages/neuron-wallet/tests/services/node.test.ts index 5cf5b39d24..3cc69e0130 100644 --- a/packages/neuron-wallet/tests/services/node.test.ts +++ b/packages/neuron-wallet/tests/services/node.test.ts @@ -91,7 +91,7 @@ describe('NodeService', () => { return { getTipBlockNumber: stubbedGetTipBlockNumber, } - } + }, } }) jest.doMock('rxjs/operators', () => { @@ -147,7 +147,7 @@ describe('NodeService', () => { return function () { return { getChain: getChainMock, - localNodeInfo: getLocalNodeInfoMock + localNodeInfo: getLocalNodeInfoMock, } } }) @@ -160,8 +160,8 @@ describe('NodeService', () => { start: stubbedStartLightNode, stop: stubbedStopLightNode, } - } - } + }, + }, } }) @@ -225,8 +225,8 @@ describe('NodeService', () => { beforeEach(async () => { const NodeService = require('../../src/services/node').default nodeService = new NodeService() - nodeService.verifyNodeVersion = () => {} - nodeService.verifyStartWithIndexer = () => {} + nodeService.getCompatibility = () => {} + nodeService.isStartWithIndexer = () => {} stubbedNetworsServiceGet.mockReturnValueOnce({ remote: BUNDLED_CKB_URL }) }) @@ -282,7 +282,7 @@ describe('NodeService', () => { beforeEach(async () => { stubbedStartCKBNode.mockRejectedValue(new Error()) await nodeService.startNode() - }); + }) it('logs error', () => { expect(stubbedLoggerInfo).toHaveBeenCalledWith('CKB: fail to start bundled CKB with error:') expect(stubbedLoggerError).toHaveBeenCalledWith(new Error()) @@ -295,19 +295,19 @@ describe('NodeService', () => { startedBundledNode: false, }) }) - }); + }) describe('start light node', () => { beforeEach(() => { stubbedNetworsServiceGet.mockReset() }) it('start light node', async () => { - stubbedNetworsServiceGet.mockReturnValueOnce({type: NetworkType.Light}) + stubbedNetworsServiceGet.mockReturnValueOnce({ type: NetworkType.Light }) await nodeService.startNode() expect(stubbedStartLightNode).toBeCalled() expect(stubbedStopCkbNode).toBeCalled() }) }) - }); + }) describe('CurrentNetworkIDSubject#subscribe', () => { let eventCallback: any const stubbedTipNumberSubjectCallback = jest.fn() @@ -315,8 +315,8 @@ describe('NodeService', () => { const NodeService = require('../../src/services/node').default nodeService = new NodeService() nodeService.tipNumberSubject.subscribe(stubbedTipNumberSubjectCallback) - nodeService.verifyNodeVersion = () => {} - nodeService.verifyStartWithIndexer = () => {} + nodeService.getCompatibility = () => {} + nodeService.isStartWithIndexer = () => {} eventCallback = stubbedCurrentNetworkIDSubjectSubscribe.mock.calls[0][0] }) it('emits disconnected event in ConnectionStatusSubject', () => { @@ -337,7 +337,7 @@ describe('NodeService', () => { nodeService.ckb.node.url = bundledNodeUrl }) stubbedStartCKBNode.mockResolvedValue(true) - stubbedNetworsServiceGet.mockReturnValue({remote: bundledNodeUrl}) + stubbedNetworsServiceGet.mockReturnValue({ remote: bundledNodeUrl }) getLocalNodeInfoMock.mockRejectedValue('not start') await nodeService.tryStartNodeOnDefaultURI() @@ -355,8 +355,8 @@ describe('NodeService', () => { describe('switches to other network', () => { beforeEach(async () => { stubbedConnectionStatusSubjectNext.mockReset() - stubbedNetworsServiceGet.mockReturnValue({remote: fakeHTTPUrl}) - await eventCallback({currentNetworkID: 'network2'}) + stubbedNetworsServiceGet.mockReturnValue({ remote: fakeHTTPUrl }) + await eventCallback({ currentNetworkID: 'network2' }) jest.advanceTimersByTime(10000) }) it('sets startedBundledNode to true in ConnectionStatusSubject', () => { @@ -367,8 +367,8 @@ describe('NodeService', () => { startedBundledNode: false, }) }) - }); - }); + }) + }) describe('with invalid url', () => { beforeEach(() => { stubbedNetworsServiceGet.mockReturnValueOnce({ remote: 'invalidurl' }) @@ -424,52 +424,77 @@ describe('NodeService', () => { expect(nodeService.getInternalNodeVersion()).toBe('0.107.0') }) }) - describe('test verify node version', () => { + describe('test verify external ckb node', () => { beforeEach(() => { const NodeService = require('../../src/services/node').default nodeService = new NodeService() - stubbedNetworsServiceGet.mockReturnValueOnce({ remote: BUNDLED_CKB_URL }) - }) - it('get internal version failed', async () => { - existsSyncMock.mockReturnValue(false) - getLocalNodeInfoMock.mockResolvedValue({}) - await nodeService.verifyNodeVersion() - expect(showMessageBoxMock).toBeCalledTimes(0) + nodeService.getNeuronCompatibilityCKB = () => ['0.109', '0.110'] + stubbedNetworsServiceGet.mockReturnValue({ remote: BUNDLED_CKB_URL }) }) - it('get internal version success and same', async () => { + it('the ckb is running external and not light client', async () => { + nodeService._isCkbNodeExternal = true existsSyncMock.mockReturnValue(true) readFileSyncMock.mockReturnValue('v0.107.0') getLocalNodeInfoMock.mockResolvedValue({ version: '0.107.0 (30e1255 2023-01-30)' }) - await nodeService.verifyNodeVersion() - expect(showMessageBoxMock).toBeCalledTimes(0) + const res = await nodeService.verifyExternalCkbNode() + expect(res).toStrictEqual({ + ckb: 0, + withIndexer: false, + }) }) - it('get internal version success and patch not same', async () => { - existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue('v0.107.1') - getLocalNodeInfoMock.mockResolvedValue({ version: '0.107.0 (30e1255 2023-01-30)' }) - await nodeService.verifyNodeVersion() - expect(showMessageBoxMock).toBeCalledTimes(0) + it('the ckb is running external and not light client, but get version failed', async () => { + nodeService._isCkbNodeExternal = true + existsSyncMock.mockReturnValue(false) + getLocalNodeInfoMock.mockResolvedValue({}) + const res = await nodeService.verifyExternalCkbNode() + expect(res).toBeUndefined() }) - it('major is same and minor is not same with major 0', async () => { - existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue('v0.107.0') - getLocalNodeInfoMock.mockResolvedValue({ version: '0.108.0 (30e1255 2023-01-30)' }) - await nodeService.verifyNodeVersion() - expect(showMessageBoxMock).toBeCalledTimes(1) + it('the ckb type is light client', async () => { + nodeService._isCkbNodeExternal = true + stubbedNetworsServiceGet.mockReturnValueOnce({ remote: BUNDLED_CKB_URL, type: NetworkType.Light }) + const res = await nodeService.verifyExternalCkbNode() + expect(res).toBeUndefined() }) - it('major is same and minor is not same with major 1', async () => { - existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue('v1.107.0') - getLocalNodeInfoMock.mockResolvedValue({ version: '1.108.0 (30e1255 2023-01-30)' }) - await nodeService.verifyNodeVersion() - expect(showMessageBoxMock).toBeCalledTimes(0) + it('the ckb is running internal', async () => { + nodeService._isCkbNodeExternal = false + const res = await nodeService.verifyExternalCkbNode() + expect(res).toBeUndefined() }) - it('major is not same', async () => { - existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue('v1.107.0') - getLocalNodeInfoMock.mockResolvedValue({ version: '0.108.0 (30e1255 2023-01-30)' }) - await nodeService.verifyNodeVersion() - expect(showMessageBoxMock).toBeCalledTimes(1) + }) + describe('test get compatibility', () => { + let VerifyCkbVersionResult: any + beforeEach(() => { + const NodeService = require('../../src/services/node').default + VerifyCkbVersionResult = require('../../src/services/node').VerifyCkbVersionResult + nodeService = new NodeService() + nodeService.getNeuronCompatibilityCKB = () => ['0.109', '0.110'] + stubbedNetworsServiceGet.mockReturnValueOnce({ remote: BUNDLED_CKB_URL }) + }) + it('get internal version success and same', () => { + expect(nodeService.getCompatibility('0.107.0', '0.107.0 (30e1255 2023-01-30)')).toBe(VerifyCkbVersionResult.Same) + }) + it('get internal version success and patch not same', () => { + expect(nodeService.getCompatibility('0.107.1', '0.107.0 (30e1255 2023-01-30)')).toBe(VerifyCkbVersionResult.Same) + }) + it('major is same and minor is not same with major 0', () => { + expect(nodeService.getCompatibility('0.107.1', '0.108.0 (30e1255 2023-01-30)')).toBe( + VerifyCkbVersionResult.ShouldUpdate + ) + }) + it('major is same and minor is not same with major 1', () => { + expect(nodeService.getCompatibility('0.107.1', '1.108.0 (30e1255 2023-01-30)')).toBe( + VerifyCkbVersionResult.ShouldUpdate + ) + }) + it('major is not same', () => { + expect(nodeService.getCompatibility('1.107.1', '0.108.0 (30e1255 2023-01-30)')).toBe( + VerifyCkbVersionResult.Incompatible + ) + }) + it('is compatible', () => { + expect(nodeService.getCompatibility('0.110.0', '0.109.0 (30e1255 2023-01-30)')).toBe( + VerifyCkbVersionResult.Compatible + ) }) }) describe('test verify start with indexer', () => { @@ -480,18 +505,48 @@ describe('NodeService', () => { }) it('start with indexer', async () => { rpcRequestMock.mockResolvedValue({}) - await nodeService.verifyStartWithIndexer() - expect(showMessageBoxMock).toBeCalledTimes(0) + const res = await nodeService.isStartWithIndexer() + expect(res).toBe(true) }) it('start without indexer', async () => { rpcRequestMock.mockResolvedValue({ error: { code: START_WITHOUT_INDEXER } }) - await nodeService.verifyStartWithIndexer() - expect(showMessageBoxMock).toBeCalledTimes(1) + const res = await nodeService.isStartWithIndexer() + expect(res).toBe(false) }) it('get indexer rpc failed', async () => { rpcRequestMock.mockRejectedValue('get tip header error') - await nodeService.verifyStartWithIndexer() - expect(showMessageBoxMock).toBeCalledTimes(1) + const res = await nodeService.isStartWithIndexer() + expect(res).toBe(false) + }) + }) + describe('test get Neuron compatibility CKB', () => { + beforeEach(() => { + const NodeService = require('../../src/services/node').default + nodeService = new NodeService() + }) + it('no compatibility file', () => { + existsSyncMock.mockReturnValue(false) + expect(nodeService.getNeuronCompatibilityCKB('0.110.0')).toBeUndefined() + }) + it('read file error', () => { + existsSyncMock.mockReturnValue(true) + readFileSyncMock.mockReturnValue(new Error('read failed')) + expect(nodeService.getNeuronCompatibilityCKB('0.110.0')).toBeUndefined() + }) + it('ckb version content is wrong', async () => { + existsSyncMock.mockReturnValue(true) + readFileSyncMock.mockReturnValue('') + expect(nodeService.getNeuronCompatibilityCKB('0.110.0')).toBeUndefined() + }) + it('no neuron version', async () => { + existsSyncMock.mockReturnValue(true) + readFileSyncMock.mockReturnValue('ckb,0.110\nNeuron,\n0.109,yes,no') + expect(nodeService.getNeuronCompatibilityCKB('0.110.0')).toBeUndefined() + }) + it('success', async () => { + existsSyncMock.mockReturnValue(true) + readFileSyncMock.mockReturnValue('ckb,0.110,0.109\nNeuron,\n0.110,yes,no') + expect(nodeService.getNeuronCompatibilityCKB('0.110.0')).toStrictEqual(['0.110']) }) }) })