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 #184 from LiskHQ/183-improve-user-experience
Browse files Browse the repository at this point in the history
Improve user experience
  • Loading branch information
sameersubudhi authored Oct 4, 2023
2 parents 0f552bf + 82b1f77 commit 32f9e45
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 64 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ The following dependencies need to be installed in order to run applications cre

| Dependencies | Version |
| ------------ | -------------- |
| NodeJS | 18.16 |
| NodeJS | ^18.16 |
| NPM | 8.3.1 or later |
| Lisk Core | 3.1.0 or later |

**NOTE**: It is important that NodeJS is installed using NVM. Please refer our documentation [here](https://lisk.com/documentation/lisk-core/v4/setup/npm.html#node-js-npm).

### System requirements

The following system requirements are recommended to run Lisk Migrator v2.0.0:
Expand Down
2 changes: 1 addition & 1 deletion config/mainnet/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"modes": ["ipc", "ws"],
"port": 7887,
"host": "127.0.0.1",
"allowedMethods": ["generator", "system", "random"]
"allowedMethods": ["generator", "system", "random", "keys", "legacy"]
},
"genesis": {
"block": {
Expand Down
2 changes: 1 addition & 1 deletion config/testnet/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"modes": ["ipc", "ws"],
"port": 7887,
"host": "127.0.0.1",
"allowedMethods": ["generator", "system", "random"]
"allowedMethods": ["generator", "system", "random", "keys", "legacy"]
},
"genesis": {
"block": {
Expand Down
25 changes: 17 additions & 8 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
*/
import { resolve } from 'path';
import { Command } from '@oclif/command';

import { APIClient } from '@liskhq/lisk-api-client';
import { Block } from '@liskhq/lisk-chain';
import { EVENT_NEW_BLOCK } from './constants';
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 { ForgingStatus } from './types';

const { getLisk32AddressFromAddress } = address;

export const captureForgingStatusAtSnapshotHeight = (
_this: Command,
Expand All @@ -28,17 +31,23 @@ export const captureForgingStatusAtSnapshotHeight = (
client.subscribe(EVENT_NEW_BLOCK, async data => {
const { block: encodedBlock } = (data as unknown) as Record<string, string>;
const newBlock = client.block.decode(Buffer.from(encodedBlock, 'hex')) as Block;

if (newBlock.header.height === snapshotHeight) {
const forgingStatus = await client.invoke('app:getForgingStatus');
if (forgingStatus.length) {
const forgingStatusJsonFilepath = resolve(outputDir, 'forgingStatus.json');
const forgingStatuses: ForgingStatus[] = await client.invoke('app:getForgingStatus');
const finalForgingStatuses: ForgingStatus[] = forgingStatuses.map(entry => ({
...entry,
lskAddress: getLisk32AddressFromAddress(Buffer.from(entry.address, 'hex')),
}));

if (finalForgingStatuses.length) {
try {
await write(forgingStatusJsonFilepath, JSON.stringify(forgingStatus, null, '\t'));
const forgingStatusJsonFilepath = resolve(outputDir, 'forgingStatus.json');
await write(forgingStatusJsonFilepath, JSON.stringify(finalForgingStatuses, null, '\t'));
_this.log(`\nFinished exporting forging status to ${forgingStatusJsonFilepath}.`);
} catch (error) {
_this.log(
`\nUnable to save the node Forging Status information to the disk, please find it below instead:\n${JSON.stringify(
forgingStatus,
finalForgingStatuses,
null,
2,
)}`,
Expand Down
68 changes: 49 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import {
} from './utils/genesis_block';
import { CreateAsset } from './createAsset';
import { ApplicationConfigV3, NetworkConfigLocal, NodeInfo } from './types';
import { installLiskCore, startLiskCore } from './utils/node';
import { resolveAbsolutePath } from './utils/fs';
import { installLiskCore, startLiskCore, isLiskCoreV3Running } from './utils/node';
import { resolveAbsolutePath, verifyOutputPath } from './utils/path';
import { execAsync } from './utils/process';

let configCoreV4: PartialApplicationConfig;
Expand All @@ -67,8 +67,7 @@ class LiskMigrator extends Command {
output: flagsParser.string({
char: 'o',
required: false,
description:
"File path to write the genesis block. If not provided, it will default to cwd/output/{v3_networkIdentifier}/genesis_block.blob. Do not use any value starting with the default data path reserved for Lisk Core: '~/.lisk/lisk-core'.",
description: `File path to write the genesis block. If not provided, it will default to cwd/output/{v3_networkIdentifier}/genesis_block.blob. Do not use any value starting with the default data path reserved for Lisk Core: '${DEFAULT_LISK_CORE_PATH}'.`,
}),
'lisk-core-v3-data-path': flagsParser.string({
char: 'd',
Expand All @@ -79,7 +78,7 @@ class LiskMigrator extends Command {
config: flagsParser.string({
char: 'c',
required: false,
description: 'Custom configuration file path for Lisk Core v3.x.',
description: 'Custom configuration file path for Lisk Core v3.1.x.',
}),
'snapshot-height': flagsParser.integer({
char: 's',
Expand All @@ -91,21 +90,22 @@ class LiskMigrator extends Command {
'auto-migrate-config': flagsParser.boolean({
required: false,
env: 'AUTO_MIGRATE_CONFIG',
description: 'Migrate user configuration automatically. Default to false.',
description: 'Migrate user configuration automatically. Defaults to false.',
default: false,
}),
'auto-start-lisk-core-v4': flagsParser.boolean({
required: false,
env: 'AUTO_START_LISK_CORE',
description: 'Start lisk core v4 automatically. Default to false.',
description:
'Start Lisk Core v4 automatically. Defaults to false. When using this flag, kindly open another terminal window to stop Lisk Core v3.1.x for when the migrator prompts.',
default: false,
}),
'page-size': flagsParser.integer({
char: 'p',
required: false,
default: 100000,
description:
'Maximum number of blocks to be iterated at once for computation. Default to 100000.',
'Maximum number of blocks to be iterated at once for computation. Defaults to 100000.',
}),
};

Expand All @@ -122,6 +122,8 @@ class LiskMigrator extends Command {
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;
Expand All @@ -138,7 +140,9 @@ class LiskMigrator extends Command {
`Verifying snapshot height to be multiples of round length i.e ${ROUND_LENGTH}`,
);
if (snapshotHeight % ROUND_LENGTH !== 0) {
this.error(`Invalid Snapshot Height: ${snapshotHeight}.`);
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');

Expand Down Expand Up @@ -250,6 +254,7 @@ class LiskMigrator extends Command {
snapshotHeight,
)) as unknown) as Block;
await createGenesisBlock(
this,
networkConstant.name,
defaultConfigFilePath,
outputDir,
Expand Down Expand Up @@ -289,37 +294,62 @@ class LiskMigrator extends Command {
);

if (isLiskCoreV3Stopped) {
let numTriesLeft = 3;
while (numTriesLeft) {
numTriesLeft -= 1;

const isCoreV3Running = await isLiskCoreV3Running(liskCoreV3DataPath);
if (!isCoreV3Running) break;

if (numTriesLeft >= 0) {
const isStopReconfirmed = await cli.confirm(
"Lisk Core v3 still running. Please stop the node, type 'yes' to proceed and 'no' to exit. [yes/no]",
);
if (!isStopReconfirmed) {
this.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(
`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!!!`,
);
}
}
}
}

const isUserConfirmed = await cli.confirm(
`Start Lisk Core with the following configuration? [yes/no] \n${util.inspect(
configCoreV4,
false,
3,
)}`,
`Start Lisk Core with the following configuration? [yes/no]
${util.inspect(configCoreV4, false, 3)} `,
);

if (isUserConfirmed) {
cli.action.start('Starting Lisk Core v4');
const network = networkConstant.name as string;
await startLiskCore(this, liskCoreV3DataPath, configCoreV4, network, outputDir);
this.log('Started Lisk Core v4 at default data directory.');
this.log(
`Started Lisk Core v4 at default data directory ('${DEFAULT_LISK_CORE_PATH}').`,
);
cli.action.stop();
} else {
this.log(
'User did not accept the migrated config. Skipping the Lisk Core v4 auto-start process.',
);
}
} else {
this.log(
'User did not confirm Lisk Core v3 node shutdown. Skipping the Lisk Core v4 auto-start process.',
this.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}`);
this.error(`Failed to auto-start Lisk Core v4.\nError: ${(err as Error).message}`);
}
} else {
this.log(
`Please copy the contents of ${snapshotDirPath} directory to 'data/legacy.db' under the Lisk Core V4 data directory (e.g: ~/.lisk/lisk-core/data/legacy.db/) in order to access legacy blockchain information.`,
`Please copy the contents of ${snapshotDirPath} directory to 'data/legacy.db' under the Lisk Core v4 data directory (e.g: ${DEFAULT_LISK_CORE_PATH}/data/legacy.db/) in order to access legacy blockchain information.`,
);
this.log('Please copy genesis block to the Lisk Core V4 network directory.');
}
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,12 @@ export interface NodeInfo {
readonly registeredModules: RegisteredModule[];
readonly backup: Backup;
}

export interface ForgingStatus {
readonly address: string;
lskAddress?: string;
readonly forging: boolean;
readonly height?: number;
readonly maxHeightPrevoted?: number;
readonly maxHeightPreviouslyForged?: number;
}
3 changes: 2 additions & 1 deletion src/utils/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ export const observeChainHeight = async (options: ObserveParams): Promise<number
return startHeight;
}

const observedProperty = options.isFinal ? 'Finalized height' : 'Height';
const progress = cli.progress({
format: `${options.label}: [{bar}] {percentage}% | Remaining: {remaining}/{total} | Height: {height}/${observedHeight} | ETA: {timeLeft}`,
format: `${options.label}: [{bar}] {percentage}% | Remaining: {remaining}/{total} | ${observedProperty}: {height}/${observedHeight} | ETA: {timeLeft}`,
fps: 2,
synchronousUpdate: false,
etaAsynchronousUpdate: false,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
NUMBER_STANDBY_VALIDATORS,
POS_INIT_ROUNDS,
} from '../constants';
import { resolveAbsolutePath } from './fs';
import { resolveAbsolutePath } from './path';

const LOG_LEVEL_PRIORITY = Object.freeze({
FATAL: 0,
Expand Down
6 changes: 0 additions & 6 deletions src/utils/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*
* Removal or modification of this copyright notice is prohibited.
*/
import { homedir } from 'os';
import * as tar from 'tar';
import fs from 'fs';
import path from 'path';
Expand Down Expand Up @@ -48,11 +47,6 @@ export const rmdir = async (directoryPath: string, options = {}): Promise<boolea
});
});

export const resolveAbsolutePath = (inputPath: string) => {
const homeDirectory = homedir();
return homeDirectory ? inputPath.replace(/^~(?=$|\/|\\)/, homeDirectory) : inputPath;
};

/* eslint-disable @typescript-eslint/no-unused-expressions */
export const copyDir = async (src: string, dest: string) => {
await fs.promises.mkdir(dest, { recursive: true });
Expand Down
5 changes: 5 additions & 0 deletions src/utils/genesis_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import * as crypto from 'crypto';
import * as fs from 'fs-extra';
import path from 'path';
import { Command } from '@oclif/command';
import { Block as BlockVersion3 } from '@liskhq/lisk-chain';
import { SNAPSHOT_TIME_GAP } from '../constants';
import { GenesisAssetEntry } from '../types';
Expand Down Expand Up @@ -48,6 +49,7 @@ export const createChecksum = async (filePath: string): Promise<string> => {
};

export const createGenesisBlock = async (
_this: Command,
network: string,
configFilepath: string,
outputDir: string,
Expand All @@ -58,6 +60,9 @@ export const createGenesisBlock = async (
const previousBlockID = blockAtSnapshotHeight.header.id.toString('hex');

const genesisBlockCreateCommand = `lisk-core genesis-block:create --network ${network} --config=${configFilepath} --output=${outputDir} --assets-file=${outputDir}/genesis_assets.json --height=${height} --previous-block-id=${previousBlockID} --timestamp=${timestamp} --export-json`;
_this.log(
`\nExecuting the following command to generate the genesis block:\n${genesisBlockCreateCommand}`,
);

await execAsync(genesisBlockCreateCommand);
};
Expand Down
15 changes: 7 additions & 8 deletions src/utils/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ import { existsSync, renameSync } from 'fs-extra';
import { PartialApplicationConfig } from 'lisk-framework';

import { execAsync } from './process';
import { copyDir, exists } from './fs';
import { isPortAvailable } from './network';
import { resolveAbsolutePath } from './path';
import { Port } from '../types';
import { getAPIClient } from '../client';
import { DEFAULT_PORT_P2P, DEFAULT_PORT_RPC, LEGACY_DB_PATH, SNAPSHOT_DIR } from '../constants';
import { copyDir, exists, resolveAbsolutePath } from './fs';

const INSTALL_LISK_CORE_COMMAND = 'npm i -g lisk-core@^4.0.0-rc.1';
const INSTALL_PM2_COMMAND = 'npm i -g pm2';
const PM2_FILE_NAME = 'pm2.migrator.config.json';
const PM2_COMMAND_START = `pm2 start ${PM2_FILE_NAME}`;

const LISK_V3_BACKUP_DATA_DIR = `${homedir()}/.lisk/lisk-core-v3`;

Expand Down Expand Up @@ -79,9 +79,6 @@ export const startLiskCore = async (
network: string,
outputDir: string,
): Promise<void | Error> => {
const isCoreV3Running = await isLiskCoreV3Running(liskCoreV3DataPath);
if (isCoreV3Running) throw new Error('Lisk Core v3 is still running.');

const networkPort = (_config?.network?.port as Port) ?? DEFAULT_PORT_P2P;
if (!(await isPortAvailable(networkPort))) {
throw new Error(`Port ${networkPort} is not available for P2P communication.`);
Expand All @@ -99,10 +96,11 @@ export const startLiskCore = async (
await installPM2();
_this.log('Finished installing pm2.');

_this.log(`Creating PM2 config at ${process.cwd()}/${PM2_FILE_NAME}`);
const pm2FilePath = path.resolve(outputDir, PM2_FILE_NAME);
_this.log(`Creating PM2 config at ${pm2FilePath}`);
const configPath = await getFinalConfigPath(outputDir, network);
fs.writeFileSync(
PM2_FILE_NAME,
pm2FilePath,
JSON.stringify(
{
name: 'lisk-core-v4',
Expand All @@ -112,7 +110,8 @@ export const startLiskCore = async (
'\t',
),
);
_this.log(`Successfully created the PM2 config at ${process.cwd()}/${PM2_FILE_NAME}`);
_this.log(`Successfully created the PM2 config at ${pm2FilePath}`);

const PM2_COMMAND_START = `pm2 start ${pm2FilePath}`;
_this.log(await execAsync(PM2_COMMAND_START));
};
Loading

0 comments on commit 32f9e45

Please sign in to comment.