Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #189 from LiskHQ/188-output-commands-to-execute-po…
Browse files Browse the repository at this point in the history
…st-migration

Output commands to execute post migration
  • Loading branch information
sameersubudhi authored Oct 23, 2023
2 parents 2ed5a63 + 53448b0 commit 540dc7e
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 113 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ module.exports = {
rules: {
'max-len': 'off', // Managed by prettier
'no-underscore-dangle': 'off', // Used for private variables and methods
'implicit-arrow-linebreak': 'off', // Prefered
'implicit-arrow-linebreak': 'off', // Preferred
'no-mixed-spaces-and-tabs': 'off', // Managed by prettier
'no-shadow': 'off',
'operator-linebreak': 'off',
'import/prefer-default-export': 'off',
'lines-between-class-members': 'off', // Off because typescript has members and methods
Expand All @@ -31,6 +32,7 @@ module.exports = {
'@typescript-eslint/no-unnecessary-boolean-literal-compare': ['error'],
'@typescript-eslint/no-unnecessary-qualifier': ['error'],
'@typescript-eslint/no-unnecessary-type-arguments': ['error'],
'@typescript-eslint/no-shadow': 'warn',
'@typescript-eslint/prefer-for-of': ['error'],
'@typescript-eslint/prefer-function-type': ['error'],
'@typescript-eslint/prefer-includes': ['error'],
Expand Down
11 changes: 6 additions & 5 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Migration Guide

This section explains how to migrate a Lisk Core v3.0.4 (or later) node to Lisk Core v4.x using the Lisk Migrator.
This section explains how to migrate a Lisk Core v3.1.0 node to Lisk Core v4.0.0 using the Lisk Migrator.

The Lisk Migrator CLI tool will generate a new genesis (snapshot) block for Lisk Core v4.x.
The new genesis block is created based on a snapshot of the existing blockchain (running on Lisk Core v3.0.4+) at a pre-determined height.
The Lisk Migrator CLI tool will generate a new genesis (snapshot) block for Lisk Core v4.0.0.
The new genesis block is created based on a snapshot of the existing blockchain (running on Lisk Core v3.1.0) at a pre-determined height.

Lisk Migrator automatically exports the node's Forging Status information to the file named `forgingStatus.json` under the output directory. In case, Lisk Migrator is unable to save to the disk, as a fallback, the Forging Status information is logged to the standard output.
Lisk Migrator automatically exports the node's Forging Status information to the file named `forgingStatus.json` under the output directory.
In case, the Lisk Migrator is unable to save to the disk, as a fallback, the Forging Status information is logged to the standard output.

<!--
Expand Down Expand Up @@ -67,7 +68,7 @@ export PATH="$PATH:$HOME/lisk-migrator/bin"
**Check the announced snapshot height**

- For Mainnet: `TBD`
- For Testnet: `TBD`
- For Testnet: `20449414`

### Run Lisk Migrator

Expand Down
19 changes: 19 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
*
* Removal or modification of this copyright notice is prohibited.
*/
import { homedir } from 'os';

import { utils } from '@liskhq/lisk-cryptography';

import { NetworkConfigLocal } from './types';
Expand Down Expand Up @@ -93,3 +95,20 @@ export const LEGACY_DB_PATH = `${DEFAULT_LISK_CORE_PATH}/${DEFAULT_DATA_DIR}/leg

export const DEFAULT_VERSION = '0.1.0';
export const EVENT_NEW_BLOCK = 'app:block:new';

export const FILE_NAME = {
COMMANDS_TO_EXEC: 'commandsToExecute.txt',
FORGING_STATUS: 'forgingStatus.json',
KEYS: 'keys.json',
};

export const enum ERROR_CODE {
DEFAULT = 0,
INVALID_CONFIG = 1,
GENESIS_BLOCK_CREATE = 2,
LISK_CORE_START = 3,
BACKUP_LEGACY_DATA_DIR = 4,
COPY_LEGACY_DB = 5,
}

export const LISK_V3_BACKUP_DATA_DIR = `${homedir()}/.lisk/lisk-core-v3`;
4 changes: 2 additions & 2 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Block } from '@liskhq/lisk-chain';
import { address } from '@liskhq/lisk-cryptography';
import { APIClient } from '@liskhq/lisk-api-client';
import { write } from './utils/fs';
import { EVENT_NEW_BLOCK } from './constants';
import { EVENT_NEW_BLOCK, FILE_NAME } from './constants';
import { ForgingStatus } from './types';

const { getLisk32AddressFromAddress } = address;
Expand All @@ -41,7 +41,7 @@ export const captureForgingStatusAtSnapshotHeight = (

if (finalForgingStatuses.length) {
try {
const forgingStatusJsonFilepath = resolve(outputDir, 'forgingStatus.json');
const forgingStatusJsonFilepath = resolve(outputDir, FILE_NAME.FORGING_STATUS);
await write(forgingStatusJsonFilepath, JSON.stringify(finalForgingStatuses, null, '\t'));
_this.log(`\nFinished exporting forging status to ${forgingStatusJsonFilepath}.`);
} catch (error) {
Expand Down
196 changes: 151 additions & 45 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import {
SNAPSHOT_DIR,
MIN_SUPPORTED_LISK_CORE_VERSION,
DEFAULT_LISK_CORE_PATH,
ERROR_CODE,
FILE_NAME,
LISK_V3_BACKUP_DATA_DIR,
LEGACY_DB_PATH,
} from './constants';
import { getAPIClient } from './client';
import {
Expand All @@ -46,14 +50,22 @@ import { captureForgingStatusAtSnapshotHeight } from './events';
import {
copyGenesisBlock,
createGenesisBlock,
getGenesisBlockCreateCommand,
writeGenesisAssets,
writeGenesisBlock,
} from './utils/genesis_block';
import { CreateAsset } from './createAsset';
import { ApplicationConfigV3, NetworkConfigLocal, NodeInfo } from './types';
import { installLiskCore, startLiskCore, isLiskCoreV3Running } from './utils/node';
import {
installLiskCore,
startLiskCore,
isLiskCoreV3Running,
getLiskCoreStartCommand,
} from './utils/node';
import { resolveAbsolutePath, verifyOutputPath } from './utils/path';
import { execAsync } from './utils/process';
import { MigratorException } from './utils/exception';
import { writeCommandsToExec } from './utils/commands';

let configCoreV4: PartialApplicationConfig;
class LiskMigrator extends Command {
Expand Down Expand Up @@ -110,48 +122,49 @@ class LiskMigrator extends Command {
};

public async run(): Promise<void> {
try {
const { flags } = this.parse(LiskMigrator);
const liskCoreV3DataPath = resolveAbsolutePath(
flags['lisk-core-v3-data-path'] ?? DEFAULT_LISK_CORE_PATH,
const { flags } = this.parse(LiskMigrator);
const liskCoreV3DataPath = resolveAbsolutePath(
flags['lisk-core-v3-data-path'] ?? DEFAULT_LISK_CORE_PATH,
);
const outputPath = flags.output ?? join(__dirname, '..', 'output');
const snapshotHeight = flags['snapshot-height'];
const customConfigPath = flags.config;
const autoMigrateUserConfig = flags['auto-migrate-config'] ?? false;
const autoStartLiskCoreV4 = flags['auto-start-lisk-core-v4'];
const pageSize = Number(flags['page-size']);

verifyOutputPath(outputPath);

const client = await getAPIClient(liskCoreV3DataPath);
const nodeInfo = (await client.node.getNodeInfo()) as NodeInfo;
const { version: appVersion, networkIdentifier } = nodeInfo;

cli.action.start('Verifying if backup height from node config matches snapshot height');
if (snapshotHeight !== nodeInfo.backup.height) {
this.error(
`Lisk Core v3 backup height (${nodeInfo.backup.height}) does not match the expected snapshot height (${snapshotHeight}).`,
);
const outputPath = flags.output ?? join(__dirname, '..', 'output');
const snapshotHeight = flags['snapshot-height'];
const customConfigPath = flags.config;
const autoMigrateUserConfig = flags['auto-migrate-config'] ?? false;
const autoStartLiskCoreV4 = flags['auto-start-lisk-core-v4'];
const pageSize = Number(flags['page-size']);

verifyOutputPath(outputPath);

const client = await getAPIClient(liskCoreV3DataPath);
const nodeInfo = (await client.node.getNodeInfo()) as NodeInfo;
const { version: appVersion, networkIdentifier } = nodeInfo;

cli.action.start('Verifying if backup height from node config matches snapshot height');
if (snapshotHeight !== nodeInfo.backup.height) {
this.error(
`Lisk Core v3 backup height (${nodeInfo.backup.height}) does not match the expected snapshot height (${snapshotHeight}).`,
);
}
cli.action.stop('Snapshot height matches backup height');

cli.action.start(
`Verifying snapshot height to be multiples of round length i.e ${ROUND_LENGTH}`,
}
cli.action.stop('Snapshot height matches backup height');

cli.action.start(
`Verifying snapshot height to be multiples of round length i.e ${ROUND_LENGTH}`,
);
if (snapshotHeight % ROUND_LENGTH !== 0) {
this.error(
`Invalid snapshot height provided: ${snapshotHeight}. It must be an exact multiple of round length (${ROUND_LENGTH}).`,
);
if (snapshotHeight % ROUND_LENGTH !== 0) {
this.error(
`Invalid snapshot height provided: ${snapshotHeight}. It must be an exact multiple of round length (${ROUND_LENGTH}).`,
);
}
cli.action.stop('Snapshot height is valid');
}
cli.action.stop('Snapshot height is valid');

const networkConstant = NETWORK_CONSTANT[networkIdentifier] as NetworkConfigLocal;
const outputDir = flags.output ? outputPath : `${outputPath}/${networkIdentifier}`;
const networkConstant = NETWORK_CONSTANT[networkIdentifier] as NetworkConfigLocal;
const outputDir = flags.output ? outputPath : `${outputPath}/${networkIdentifier}`;

// Ensure the output directory is present
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
// Ensure the output directory is present
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
const filePathCommandsToExec = `${outputDir}/${FILE_NAME.COMMANDS_TO_EXEC}`;

try {
// Asynchronously capture the node's Forging Status information at the snapshot height
// This information is necessary for the node operators to enable generator post-migration without getting PoM'd
captureForgingStatusAtSnapshotHeight(this, client, snapshotHeight, outputDir);
Expand Down Expand Up @@ -235,7 +248,12 @@ class LiskMigrator extends Command {
const isValidConfig = await validateConfig(migratedConfigV4);
cli.action.stop();

if (!isValidConfig) throw new Error('Migrated user configuration is invalid.');
if (!isValidConfig) {
throw new MigratorException(
'Migrated user configuration is invalid.',
ERROR_CODE.INVALID_CONFIG,
);
}

cli.action.start(`Exporting user configuration to the path: ${outputDir}`);
await writeConfig(migratedConfigV4, outputDir);
Expand Down Expand Up @@ -306,13 +324,13 @@ class LiskMigrator extends Command {
"Lisk Core v3 still running. Please stop the node, type 'yes' to proceed and 'no' to exit. [yes/no]",
);
if (!isStopReconfirmed) {
this.error(
throw new Error(
`Cannot proceed with Lisk Core v4 auto-start. Please continue manually. In order to access legacy blockchain information posts-migration, please copy the contents of the ${snapshotDirPath} directory to 'data/legacy.db' under the Lisk Core v4 data directory (e.g: ${DEFAULT_LISK_CORE_PATH}/data/legacy.db/). Exiting!!!`,
);
} else if (numTriesLeft === 0 && isStopReconfirmed) {
const isCoreV3StillRunning = await isLiskCoreV3Running(liskCoreV3DataPath);
if (isCoreV3StillRunning) {
this.error(
throw new Error(
`Cannot auto-start Lisk Core v4 as Lisk Core v3 is still running. Please continue manually. In order to access legacy blockchain information posts-migration, please copy the contents of the ${snapshotDirPath} directory to 'data/legacy.db' under the Lisk Core v4 data directory (e.g: ${DEFAULT_LISK_CORE_PATH}/data/legacy.db/). Exiting!!!`,
);
}
Expand All @@ -339,13 +357,16 @@ class LiskMigrator extends Command {
);
}
} else {
this.error(
throw new Error(
`User did not confirm Lisk Core v3 node shutdown. Skipping the Lisk Core v4 auto-start process. Please continue manually. In order to access legacy blockchain information posts-migration, please copy the contents of the ${snapshotDirPath} directory to 'data/legacy.db' under the Lisk Core v4 data directory (e.g: ${DEFAULT_LISK_CORE_PATH}/data/legacy.db/). Exiting!!!`,
);
}
} catch (err) {
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
this.error(`Failed to auto-start Lisk Core v4.\nError: ${(err as Error).message}`);
const errorMsg = `Failed to auto-start Lisk Core v4.\nError: ${(err as Error).message}`;
throw new MigratorException(
errorMsg,
err instanceof MigratorException ? err.code : ERROR_CODE.LISK_CORE_START,
);
}
} else {
this.log(
Expand All @@ -354,9 +375,94 @@ class LiskMigrator extends Command {
this.log('Please copy genesis block to the Lisk Core V4 network directory.');
}
} catch (error) {
this.error(error as string);
const commandsToExecute: string[] = [];
const code = Number(`${(error as MigratorException).code}`);

const basicStartCommand = `lisk-core start --network ${networkConstant.name}`;
const liskCoreStartCommand = getLiskCoreStartCommand() ?? basicStartCommand;

const backupLegacyDataDirCommand = `mv ${liskCoreV3DataPath} ${LISK_V3_BACKUP_DATA_DIR}`;
const copyLegacyDBCommand = `cp -r ${
(resolve(LISK_V3_BACKUP_DATA_DIR, SNAPSHOT_DIR), LEGACY_DB_PATH)
}`;

if (
[ERROR_CODE.DEFAULT, ERROR_CODE.INVALID_CONFIG, ERROR_CODE.GENESIS_BLOCK_CREATE].includes(
code,
)
) {
const genesisBlockCreateCommand = getGenesisBlockCreateCommand();
commandsToExecute.push(
'\n',
'## Create the genesis block',
'## NOTE: This requires installing Lisk Core v4 locally. Please visit https://lisk.com/documentation/lisk-core/v4/setup/npm.html for further instructions',
'\n',
);
commandsToExecute.push(genesisBlockCreateCommand);
commandsToExecute.push('\n', '-----------------------------------------------------', '\n');
}

if (
[
ERROR_CODE.DEFAULT,
ERROR_CODE.INVALID_CONFIG,
ERROR_CODE.GENESIS_BLOCK_CREATE,
ERROR_CODE.BACKUP_LEGACY_DATA_DIR,
].includes(code)
) {
commandsToExecute.push('\n', '## Backup Lisk Core v3 data directory', '\n');
commandsToExecute.push(backupLegacyDataDirCommand);
commandsToExecute.push('\n', '-----------------------------------------------------', '\n');
}

if (
[
ERROR_CODE.DEFAULT,
ERROR_CODE.INVALID_CONFIG,
ERROR_CODE.GENESIS_BLOCK_CREATE,
ERROR_CODE.BACKUP_LEGACY_DATA_DIR,
ERROR_CODE.COPY_LEGACY_DB,
].includes(code)
) {
commandsToExecute.push(
'\n',
'## Copy legacy (v3) blockchain information to Lisk Core v4 legacy.db',
'\n',
);
commandsToExecute.push(copyLegacyDBCommand);
commandsToExecute.push('\n', '-----------------------------------------------------', '\n');
}

if (
[
ERROR_CODE.DEFAULT,
ERROR_CODE.INVALID_CONFIG,
ERROR_CODE.GENESIS_BLOCK_CREATE,
ERROR_CODE.BACKUP_LEGACY_DATA_DIR,
ERROR_CODE.COPY_LEGACY_DB,
ERROR_CODE.LISK_CORE_START,
].includes(code)
) {
commandsToExecute.push(
'\n',
'## Lisk Core v4 start command - Please modify if necessary',
'\n',
);
commandsToExecute.push(liskCoreStartCommand);
commandsToExecute.push('\n', '-----------------------------------------------------', '\n');
}

await writeCommandsToExec(this, networkConstant, outputDir, commandsToExecute);

this.error(
`Migrator could not finish execution successfully due to: ${
(error as Error).message
}\nPlease check the commands to be executed in the file: ${filePathCommandsToExec}`,
);
}

await writeCommandsToExec(this, networkConstant, outputDir);

this.log('Successfully finished migration. Exiting!!!');
process.exit(0);
}
Expand Down
Loading

0 comments on commit 540dc7e

Please sign in to comment.