diff --git a/framework/src/node/node.ts b/framework/src/node/node.ts index b63c2cb2082..4358b9fe8b1 100644 --- a/framework/src/node/node.ts +++ b/framework/src/node/node.ts @@ -71,6 +71,7 @@ import { EVENT_SYNCHRONIZER_SYNC_REQUIRED } from './synchronizer/base_synchroniz import { Network } from './network'; import { BaseAsset, BaseModule } from '../modules'; import { Bus } from '../controller/bus'; +import { backupDatabase } from './utils/backup'; const forgeInterval = 1000; const { EVENT_NEW_BLOCK, EVENT_DELETE_BLOCK, EVENT_VALIDATORS_CHANGED } = chainEvents; @@ -733,19 +734,12 @@ export class Node { this._options.backup.height > 0 && this._options.backup.height === block.header.height ) { - const backupPath = path.resolve(this._dataPath, 'backup'); - // if backup already exist, it should remove the directory and create a new checkpoint - if (fs.existsSync(backupPath)) { - fs.removeSync(backupPath); - } - this._blockchainDB - .checkpoint(backupPath) - .catch(err => - this._logger.fatal( - { err: err as Error, height: this._options.backup.height, path: backupPath }, - 'Fail to create backup', - ), - ); + backupDatabase(this._dataPath, this._blockchainDB).catch(err => + this._logger.fatal( + { err: err as Error, heght: this._options.backup.height }, + 'Failed to create backup', + ), + ); } // Remove any transactions from the pool on new block diff --git a/framework/src/node/utils/backup.ts b/framework/src/node/utils/backup.ts new file mode 100644 index 00000000000..96a764750e6 --- /dev/null +++ b/framework/src/node/utils/backup.ts @@ -0,0 +1,25 @@ +/* + * Copyright © 2023 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ +import * as path from 'path'; +import * as fs from 'fs'; +import { Database } from '@liskhq/lisk-db'; + +export const backupDatabase = async (dataPath: string, db: Database) => { + const backupPath = path.resolve(dataPath, 'backup'); + // if backup already exist, it should remove the directory and create a new checkpoint + if (fs.existsSync(backupPath)) { + fs.rmSync(backupPath, { recursive: true, force: true }); + } + await db.checkpoint(backupPath); +}; diff --git a/framework/test/unit/node/utils/backup.spec.ts b/framework/test/unit/node/utils/backup.spec.ts new file mode 100644 index 00000000000..aad1462521d --- /dev/null +++ b/framework/test/unit/node/utils/backup.spec.ts @@ -0,0 +1,60 @@ +/* + * Copyright © 2023 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import { Database } from '@liskhq/lisk-db'; +import { getRandomBytes } from '@liskhq/lisk-cryptography'; +import { backupDatabase } from '../../../../src/node/utils/backup'; + +describe('backup', () => { + const getDataPath = (name: string) => path.join(os.tmpdir(), Date.now().toString(), name); + + it('should create backup', async () => { + const dbName = 'db-1'; + const dataPath = getDataPath(dbName); + const db = new Database(path.join(dataPath, dbName)); + const key = getRandomBytes(10); + await db.set(key, getRandomBytes(20)); + + await backupDatabase(dataPath, db); + + expect(fs.existsSync(path.join(dataPath, 'backup'))).toBeTrue(); + db.close(); + }); + + it('should remove old backup and create new one if exist', async () => { + const dbName = 'db-2'; + const dataPath = getDataPath(dbName); + const db = new Database(path.join(dataPath, dbName)); + const key = getRandomBytes(10); + await db.set(key, getRandomBytes(20)); + + await backupDatabase(dataPath, db); + const key2 = getRandomBytes(10); + await db.set(key2, getRandomBytes(20)); + + expect(fs.existsSync(path.join(dataPath, 'backup'))).toBeTrue(); + + await backupDatabase(dataPath, db); + + expect(fs.existsSync(path.join(dataPath, 'backup'))).toBeTrue(); + db.close(); + const backupDB = new Database(path.join(dataPath, 'backup')); + + await expect(backupDB.has(key2)).resolves.toBeTrue(); + backupDB.close(); + }); +});