diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx index f1918d5c00..0246117032 100644 --- a/packages/neuron-ui/src/components/NervosDAO/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx @@ -113,6 +113,7 @@ const NervosDAO = () => { cacheTipBlockNumber, currentTimestamp: Date.now(), url: getCurrentUrl(networkID, networks), + networkID, }) const MemoizedRecords = useMemo(() => { diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 74a316aab5..082db564b8 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -209,6 +209,7 @@ export const reducer = produce((state: Draft, action: cacheTipBlockNumber: action.payload.cacheTipBlockNumber, currentTimestamp: Date.now(), url: getCurrentUrl(state.chain.networkID, state.settings.networks), + networkID: state.chain.networkID, }), } break diff --git a/packages/neuron-ui/src/utils/getSyncStatus.ts b/packages/neuron-ui/src/utils/getSyncStatus.ts index 1059b52f33..9067d65a9f 100644 --- a/packages/neuron-ui/src/utils/getSyncStatus.ts +++ b/packages/neuron-ui/src/utils/getSyncStatus.ts @@ -5,6 +5,7 @@ const TEN_MINS = 10 * 60 * 1000 let blockNumber10MinAgo: number = -1 let timestamp10MinAgo: number | undefined let prevUrl: string | undefined +let prevNetworkID: string | undefined export const getSyncStatus = ({ bestKnownBlockNumber, @@ -12,17 +13,26 @@ export const getSyncStatus = ({ cacheTipBlockNumber, currentTimestamp, url, + networkID, }: { bestKnownBlockNumber: number bestKnownBlockTimestamp: number cacheTipBlockNumber: number currentTimestamp: number url: string | undefined + networkID: string }) => { - if ((!timestamp10MinAgo && bestKnownBlockNumber >= 0) || (prevUrl && url !== prevUrl && bestKnownBlockNumber >= 0)) { + if ( + // !timestamp10MinAgo means start sync for the first time + (!timestamp10MinAgo && bestKnownBlockNumber >= 0) || + // prevUrl for change the network remote(change network or eidt the network) + // prevNetworkID for change the network, sometime change network the remote is same. light client mainnet -> testnet + (((prevUrl && url !== prevUrl) || (prevNetworkID && prevNetworkID !== networkID)) && bestKnownBlockNumber >= 0) + ) { timestamp10MinAgo = currentTimestamp blockNumber10MinAgo = bestKnownBlockNumber prevUrl = url + prevNetworkID = networkID } const now = Math.floor(currentTimestamp / 1000) * 1000 diff --git a/packages/neuron-wallet/src/controllers/export-debug.ts b/packages/neuron-wallet/src/controllers/export-debug.ts index 76c0a995e0..38c9af3d38 100644 --- a/packages/neuron-wallet/src/controllers/export-debug.ts +++ b/packages/neuron-wallet/src/controllers/export-debug.ts @@ -12,6 +12,7 @@ import redistCheck from '../utils/redist-check' import SettingsService from '../services/settings' import { generateRPC } from '../utils/ckb-rpc' import { CKBLightRunner } from '../services/light-runner' +import { LIGHT_CLIENT_MAINNET, LIGHT_CLIENT_TESTNET } from '../utils/const' export default class ExportDebugController { #I18N_PATH = 'export-debug-info' @@ -162,10 +163,13 @@ export default class ExportDebugController { } private addBundledCKBLightClientLog() { - const logPath = CKBLightRunner.getInstance().logPath - if (!fs.existsSync(logPath)) { - return + const mainnetLogPath = CKBLightRunner.getInstance().getLogPath(LIGHT_CLIENT_MAINNET) + const testnetLogPath = CKBLightRunner.getInstance().getLogPath(LIGHT_CLIENT_TESTNET) + if (fs.existsSync(mainnetLogPath)) { + this.archive.file(mainnetLogPath, { name: 'bundled-ckb-lignt-client-mainnet.log' }) + } + if (fs.existsSync(testnetLogPath)) { + this.archive.file(testnetLogPath, { name: 'bundled-ckb-lignt-client-testnet.log' }) } - this.archive.file(logPath, { name: 'bundled-ckb-lignt-client.log' }) } } diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts index 1aa9ba5c3e..0439e1c9fd 100644 --- a/packages/neuron-wallet/src/services/ckb-runner.ts +++ b/packages/neuron-wallet/src/services/ckb-runner.ts @@ -119,16 +119,16 @@ export const startCkbNode = async () => { stdio[1] = 'pipe' } logger.info(`CKB:\tckb full node will with rpc port ${rpcPort}, listen port ${listenPort}, with options`, options) - ckb = spawn(ckbBinary(), options, { stdio }) + const currentProcess = spawn(ckbBinary(), options, { stdio }) - ckb.stderr?.on('data', data => { + currentProcess.stderr?.on('data', data => { const dataString: string = data.toString() logger.error('CKB:\trun fail:', dataString) if (dataString.includes('CKB wants to migrate the data into new format')) { MigrateSubject.next({ type: 'need-migrate' }) } }) - ckb.stdout?.on('data', data => { + currentProcess.stdout?.on('data', data => { const dataString: string = data.toString() if ( dataString.includes( @@ -142,18 +142,24 @@ export const startCkbNode = async () => { } }) - ckb.on('error', error => { + currentProcess.on('error', error => { logger.error('CKB:\trun fail:', error) isLookingValidTarget = false - ckb = null + if (Object.is(ckb, currentProcess)) { + ckb = null + } }) - ckb.on('close', () => { + currentProcess.on('close', () => { logger.info('CKB:\tprocess closed') isLookingValidTarget = false - ckb = null + if (Object.is(ckb, currentProcess)) { + ckb = null + } }) + ckb = currentProcess + removeOldIndexerIfRunSuccess() } diff --git a/packages/neuron-wallet/src/services/light-runner.ts b/packages/neuron-wallet/src/services/light-runner.ts index 59c1b6d0fb..72f1d0a032 100644 --- a/packages/neuron-wallet/src/services/light-runner.ts +++ b/packages/neuron-wallet/src/services/light-runner.ts @@ -65,7 +65,7 @@ abstract class NodeRunner { async stop() { return new Promise(resolve => { if (this.runnerProcess) { - logger.info('Runner:\tkilling node') + logger.info('Runner:\tkilling node', this.runnerProcess.pid) this.runnerProcess.once('close', () => resolve()) this.runnerProcess.kill() this.runnerProcess = undefined @@ -79,7 +79,7 @@ abstract class NodeRunner { export class CKBLightRunner extends NodeRunner { protected networkType: NetworkType = NetworkType.Light protected binaryName: string = 'ckb-light-client' - protected logStream?: fs.WriteStream + protected logStream: Map = new Map() protected _port: number = 9000 static getInstance(): CKBLightRunner { @@ -143,34 +143,41 @@ export class CKBLightRunner extends NodeRunner { }) this.runnerProcess = runnerProcess - if (!this.logStream) { - this.logStream = fs.createWriteStream(this.logPath) + const network = NetworksService.getInstance().getCurrent() + if (!this.logStream.get(network.id)) { + this.logStream.set(network.id, fs.createWriteStream(this.getLogPath())) } + const logStream = this.logStream.get(network.id) runnerProcess.stderr && runnerProcess.stderr.on('data', data => { const dataString: string = data.toString() logger.error('CKB Light Runner:\trun fail:', dataString) - this.logStream?.write(data) + logStream?.write(data) }) runnerProcess.stdout && runnerProcess.stdout.on('data', data => { - this.logStream?.write(data) + logStream?.write(data) }) - runnerProcess.on('error', error => { + runnerProcess.once('error', error => { logger.error('CKB Light Runner:\trun fail:', error) - this.runnerProcess = undefined + if (Object.is(this.runnerProcess, runnerProcess)) { + this.runnerProcess = undefined + } }) - runnerProcess.on('close', () => { + runnerProcess.once('close', () => { logger.info('CKB Light Runner:\tprocess closed') - this.runnerProcess = undefined + if (Object.is(this.runnerProcess, runnerProcess)) { + this.runnerProcess = undefined + } }) } - get logPath() { - return path.join(logger.transports.file.getFile().path, '..', 'light_client_run.log') + getLogPath(chain?: string) { + const fileName = chain ?? NetworksService.getInstance().getCurrent().chain + return path.join(logger.transports.file.getFile().path, '..', `${fileName}.log`) } async clearNodeCache(): Promise { diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts index f59d6e035e..37e302e4d9 100644 --- a/packages/neuron-wallet/src/services/node.ts +++ b/packages/neuron-wallet/src/services/node.ts @@ -3,7 +3,7 @@ import path from 'path' import { BI } from '@ckb-lumos/bi' import { app as electronApp, dialog, shell, app } from 'electron' import { t } from 'i18next' -import { interval, BehaviorSubject, merge } from 'rxjs' +import { interval, BehaviorSubject, merge, Subject } from 'rxjs' import { distinctUntilChanged, sampleTime, flatMap, delay, retry, debounceTime } from 'rxjs/operators' import env from '../env' import { ConnectionStatusSubject } from '../models/subjects/node' @@ -42,6 +42,7 @@ class NodeService { public tipNumberSubject = new BehaviorSubject('0') public connectionStatusSubject = new BehaviorSubject(false) + #startNodeSubject = new Subject() private _tipBlockNumber: string = '0' #startedBundledNode: boolean = false @@ -53,6 +54,7 @@ class NodeService { this.start() this.syncConnectionStatus() CurrentNetworkIDSubject.subscribe(this.whenNetworkUpdate) + this.#startNodeSubject.pipe(debounceTime(1000)).subscribe(this.startNode) } public get tipBlockNumber(): string { @@ -77,17 +79,19 @@ class NodeService { } private whenNetworkUpdate = () => { + this.stop?.() this.#startedBundledNode = false this.tipNumberSubject.next('0') this.connectionStatusSubject.next(false) } public start = () => { - const { unsubscribe } = this.tipNumber() - this.stop = unsubscribe + this.stop?.() + const subscribe = this.tipNumber() + this.stop = subscribe.unsubscribe.bind(subscribe) } - public stop: (() => void) | null = null + public stop?: () => void public tipNumber = () => { return interval(this.intervalTime) @@ -122,8 +126,7 @@ class NodeService { if (this.delayTime < 10 * this.intervalTime) { this.delayTime = 2 * this.intervalTime } - const { unsubscribe } = this.tipNumber() - this.stop = unsubscribe + this.start() } ) } @@ -134,11 +137,12 @@ class NodeService { if (isDefaultCKBNeedStart) { logger.info('CKB:\texternal RPC on default uri not detected, starting bundled CKB node.') const redistReady = await redistCheck() - await (redistReady ? this.startNode() : this.showGuideDialog()) + await (redistReady ? this.#startNodeSubject.next() : this.showGuideDialog()) await startMonitor() } else { logger.info('CKB:\texternal RPC on default uri detected, skip starting bundled CKB node.') } + this.start() } public async verifyExternalCkbNode() { @@ -170,7 +174,7 @@ class NodeService { } } - public async startNode() { + public startNode = async () => { try { const network = NetworksService.getInstance().getCurrent() if (network.type === NetworkType.Light) { @@ -191,6 +195,7 @@ class NodeService { public async startNodeIgnoreExternal() { logger.info('CKB:\tignore running external node, and start node with another port') await stopMonitor('ckb') + this.start() const redistReady = await redistCheck() await (redistReady ? this.startNode() : this.showGuideDialog()) await startMonitor() diff --git a/packages/neuron-wallet/tests/controllers/export-debug.test.ts b/packages/neuron-wallet/tests/controllers/export-debug.test.ts index 3d25983003..1bbfbac015 100644 --- a/packages/neuron-wallet/tests/controllers/export-debug.test.ts +++ b/packages/neuron-wallet/tests/controllers/export-debug.test.ts @@ -78,7 +78,9 @@ jest.mock('../../src/services/light-runner', () => { CKBLightRunner: { getInstance() { return { - logPath: '', + getLogPath() { + return '' + }, } }, }, diff --git a/packages/neuron-wallet/tests/services/node.test.ts b/packages/neuron-wallet/tests/services/node.test.ts index 726423e026..12eaadb52b 100644 --- a/packages/neuron-wallet/tests/services/node.test.ts +++ b/packages/neuron-wallet/tests/services/node.test.ts @@ -1,6 +1,7 @@ import { distinctUntilChanged, sampleTime, flatMap, delay, retry } from 'rxjs/operators' import { BUNDLED_CKB_URL, START_WITHOUT_INDEXER } from '../../src/utils/const' import { NetworkType } from '../../src/models/network' +import { scheduler } from 'timers/promises' describe('NodeService', () => { let nodeService: any @@ -9,7 +10,6 @@ describe('NodeService', () => { const stubbedStartLightNode = jest.fn() const stubbedStopLightNode = jest.fn() const stubbedConnectionStatusSubjectNext = jest.fn() - const stubbedCKBSetNode = jest.fn() const stubbedGetTipBlockNumber = jest.fn() const stubbedRxjsDebounceTime = jest.fn() const stubbedCurrentNetworkIDSubjectSubscribe = jest.fn() @@ -37,7 +37,6 @@ describe('NodeService', () => { stubbedStartCKBNode.mockReset() stubbedStopCkbNode.mockReset() stubbedConnectionStatusSubjectNext.mockReset() - stubbedCKBSetNode.mockReset() stubbedGetTipBlockNumber.mockReset() stubbedCurrentNetworkIDSubjectSubscribe.mockReset() stubbedNetworsServiceGet.mockReset() @@ -351,19 +350,16 @@ describe('NodeService', () => { describe('targets to bundled node', () => { const bundledNodeUrl = 'http://127.0.0.1:8114' beforeEach(async () => { - stubbedCKBSetNode.mockImplementation(() => { - nodeService.ckb.node.url = bundledNodeUrl - }) stubbedStartCKBNode.mockResolvedValue(true) redistCheckMock.mockResolvedValue(true) stubbedNetworsServiceGet.mockReturnValue({ remote: bundledNodeUrl, readonly: true }) getLocalNodeInfoMock.mockRejectedValue('not start') await nodeService.tryStartNodeOnDefaultURI() - + await scheduler.wait(1500) jest.advanceTimersByTime(10000) }) it('sets startedBundledNode to true in ConnectionStatusSubject', () => { - expect(stubbedConnectionStatusSubjectNext).toHaveBeenCalledWith({ + expect(stubbedConnectionStatusSubjectNext).toHaveBeenLastCalledWith({ url: bundledNodeUrl, connected: false, isBundledNode: true,