diff --git a/desktop/NodeManager.ts b/desktop/NodeManager.ts index 69f0e9ca4..49480fa98 100644 --- a/desktop/NodeManager.ts +++ b/desktop/NodeManager.ts @@ -329,11 +329,33 @@ class NodeManager extends AbstractManager { isLocalStateFarBehind(qsStatus, 100) ) { await this.runQuicksyncPrompt(qsStatus); + } else if ( + qsStatus.incremental && + qsStatus.db >= qsStatus.incremental.from && + qsStatus.incremental.to >= qsStatus.current - 10 + ) { + hideGenericModal(this.mainWindow.webContents); + const isForce = await showGenericPrompt(this.mainWindow.webContents, { + title: 'Quick sync is not needed', + message: [ + `Incremental quicksync available from layer ${qsStatus.incremental.from} to ${qsStatus.incremental.to}`, + `Latest layer in your database: ${qsStatus.db}`, + `Current layer in the network: ${qsStatus.current}`, + '', + 'Your node is nearly synced. We recommend you to keep using your local state.', + 'Do you want to incrementally update to the trusted state anyway?', + ].join('\n'), + confirmTitle: 'Update incrementally!', + cancelTitle: 'No, thanks!', + cancelTimeout: 60, + }); + isForce && (await this.runQuicksyncDownload()); } else { hideGenericModal(this.mainWindow.webContents); const isForce = await showGenericPrompt(this.mainWindow.webContents, { title: 'Quick sync is not needed', message: [ + 'Incremental update is not available.', `Latest layer in your database: ${qsStatus.db}`, `Latest layer in the trusted state: ${qsStatus.available}`, `Current layer in the network: ${qsStatus.current}`, @@ -341,11 +363,11 @@ class NodeManager extends AbstractManager { 'Your node is nearly synced. We recommend you to keep using your local state.', 'Do you want to download the trusted state anyway?', ].join('\n'), - confirmTitle: 'Download anyway!', + confirmTitle: 'Download entire state!', cancelTitle: 'No, thanks!', cancelTimeout: 60, }); - isForce && (await this.runQuicksyncDownload()); + isForce && (await this.runQuicksyncDownload(isForce)); } } catch (err) { logger.error('runQuicksyncByButton', err); @@ -484,9 +506,19 @@ class NodeManager extends AbstractManager { try { hideGenericModal(this.mainWindow.webContents); + const isIncrementalAvailable = + qsStatus.incremental !== null && + qsStatus.db >= qsStatus.incremental.from && + qsStatus.db < qsStatus.incremental.to; + const prompt = await showGenericPrompt(this.mainWindow.webContents, { title: 'Run a Quicksync?', message: [ + `Incremental update is ${ + isIncrementalAvailable + ? `available from layer ${qsStatus.incremental?.from} to ${qsStatus.incremental?.to}` + : 'not available' + }`, `Latest layer in your database: ${qsStatus.db}`, `Latest layer in the trusted state: ${qsStatus.available}`, `Current layer in the network: ${qsStatus.current}`, @@ -497,7 +529,7 @@ class NodeManager extends AbstractManager { '', 'You can run the Quicksync from the settings page later.', ].join('\n'), - confirmTitle: 'Yes, download it!', + confirmTitle: 'Yes, run quicksync!', cancelTitle: 'Sync as usual', cancelTimeout: 60, }); @@ -517,24 +549,33 @@ class NodeManager extends AbstractManager { const resume = await showGenericPrompt(this.mainWindow.webContents, { title: 'Resume interrupted quicksync?', message: [ - 'You have an incomplete download:', + 'You have an incomplete download of the entire state:', `Layer: ${qsStatus.paused.layer}`, `Progress: ${downloaded} MB / ${total} MB (${percent}%)`, '', 'Do you want to resume downloading it?', - `Or download from scratch for layer: ${qsStatus.available}?`, + isIncrementalAvailable + ? 'Or delete downloaded data and run incremental quicksync?' + : `Or download from scratch for layer: ${qsStatus.available}?`, ].join('\n'), confirmTitle: 'Yes, resume', - cancelTitle: 'Download from scratch', + cancelTitle: isIncrementalAvailable + ? 'Run incremental quicksync' + : 'Download from scratch', }); - if (!resume) { - const { urlFile, downloadFile } = this.getQuicksyncFiles( - this.getNodeDataPath() - ); - await fse.remove(urlFile); - await fse.remove(downloadFile); + if (resume) { + // Continue downloading + await this.runQuicksyncDownload(true); + return; } + // Otherwise clean up downloaded files + const { urlFile, downloadFile } = this.getQuicksyncFiles( + this.getNodeDataPath() + ); + await fse.remove(urlFile); + await fse.remove(downloadFile); } + // And run quicksync as usual await this.runQuicksyncDownload(); } } catch (err) { @@ -778,6 +819,9 @@ class NodeManager extends AbstractManager { }; }; + private getNodeStateFile = (nodeData: string) => + path.resolve(nodeData, 'state.sql'); + private checkForPausedQuicksync = async ( nodeData: string ): Promise => { @@ -821,10 +865,74 @@ class NodeManager extends AbstractManager { } }; + private checkForIncrementalQuicksync = async (nodeDataDir: string) => { + const bin = getQuicksyncPath(); + const stateFile = path.resolve(nodeDataDir, 'state.sql'); + + return new Promise((resolve, reject) => { + const args = ['incremental-check', '--state-sql', stateFile]; + const process = spawn(bin, args); + logger.log('runQuicksync:check', `${bin} ${args.join(' ')}`); + + let stdout = ''; + let stderr = ''; + process.stdout.on('data', (data) => { + stdout += data; + }); + process.stderr.on('data', (data) => { + stderr += data; + }); + + process.on('error', (err) => { + reject( + new Error( + `Cannot run Quicksync tool to check the database: ${err.message}` + ) + ); + }); + + process.on('close', async (code) => { + if (code !== 0) { + // TODO: Better error for known codes + return reject( + new Error( + `Quicksync tool failed with exit code ${code}:\n${stderr}` + ) + ); + } + + const stats = stdout + .split('\n') + .filter((l) => l.startsWith('Possible to restore')); + + if (stats.length !== 2) { + return reject( + new Error( + `Unknown result of running Quicksync tool:\n${stdout}\n${stderr}` + ) + ); + } + + const result: QuicksyncStatus['incremental'] = { + from: parseInt(stats[0].match(/(\d+)/)?.[0] || '0', 10), + to: parseInt(stats[1].match(/(\d+)/)?.[0] || '0', 10), + }; + + logger.log('runQuicksyncIncrementalCheck', result); + return resolve(result); + }); + }).catch(() => { + return null; + }); + }; + + private quicksyncStatus: QuicksyncStatus | null = null; + private runQuicksyncCheck = async () => { const bin = getQuicksyncPath(); const nodeDataDir = this.getNodeDataPath(); const paused = await this.checkForPausedQuicksync(nodeDataDir); + const incremental = await this.checkForIncrementalQuicksync(nodeDataDir); return new Promise((resolve, reject) => { const args = [ 'check', @@ -886,8 +994,10 @@ class NodeManager extends AbstractManager { current, available, paused, + incremental, }; + this.quicksyncStatus = result; this.sendToMainWindow(ipcConsts.UPDATE_QUICKSYNC_STATUS, result); logger.log('runQuicksyncCheck', result); return resolve(result); @@ -895,7 +1005,9 @@ class NodeManager extends AbstractManager { }); }; - private runQuicksyncDownload = async () => { + private runQuicksyncDownload = async ( + forceEntire = false + ): Promise => { if (this.nodeProcess) { showGenericModal(this.mainWindow.webContents, { title: 'Quicksyncing...', @@ -912,17 +1024,30 @@ class NodeManager extends AbstractManager { } const bin = getQuicksyncPath(); - const args = [ - 'download', - '--node-data', - this.getNodeDataPath(), - '--go-spacemesh-path', - getNodePath(), - ]; + + const nodeDataPath = this.getNodeDataPath(); + const stateFile = this.getNodeStateFile(nodeDataPath); + + const isIncrementalAvailable = + this.quicksyncStatus?.incremental && + this.quicksyncStatus.db >= this.quicksyncStatus.incremental.from && + this.quicksyncStatus.db < this.quicksyncStatus.incremental.to; + + const shouldQuicksyncIncrementally = !forceEntire && isIncrementalAvailable; + + const args = shouldQuicksyncIncrementally + ? ['incremental', '--state-sql', stateFile] + : [ + 'download', + '--node-data', + nodeDataPath, + '--go-spacemesh-path', + getNodePath(), + ]; logger.log('runQuicksync:download', `${bin} ${args.join(' ')}`); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (!isFileExists(bin)) { reject(new Error(`Quicksync tool is not found: ${bin}`)); return; diff --git a/node/use-version-quicksync b/node/use-version-quicksync index 00a2c6f10..f5efc86be 100644 --- a/node/use-version-quicksync +++ b/node/use-version-quicksync @@ -1 +1 @@ -v0.1.16 +v0.2.0-alpha.3 diff --git a/shared/types/quicksync.ts b/shared/types/quicksync.ts index 6ff57a9ca..8ddbf0abb 100644 --- a/shared/types/quicksync.ts +++ b/shared/types/quicksync.ts @@ -9,4 +9,8 @@ export interface QuicksyncStatus { current: number; available: number; paused: PausedQuicksyncStatus | null; + incremental: { + from: number; + to: number; + } | null; }