From 2ccfdf159b48e80059ebfc004feaecdb50fe6274 Mon Sep 17 00:00:00 2001 From: Stefan Stefanov Date: Mon, 18 Dec 2023 14:32:41 +0200 Subject: [PATCH] feat: Adding debug mode to local node. Signed-off-by: Stefan Stefanov --- .env | 3 +- README.md | 4 + src/data/StateData.ts | 12 +++ src/services/CLIService.ts | 45 +++++++---- src/services/record-parser/src/Parser.java | 2 +- src/state/DebugState.ts | 87 +++++++++++++++++++++- src/state/InitState.ts | 5 ++ src/state/StartState.ts | 2 +- src/types/CLIOptions.ts | 2 + 9 files changed, 143 insertions(+), 19 deletions(-) diff --git a/.env b/.env index 0464817b..051e70e0 100644 --- a/.env +++ b/.env @@ -16,7 +16,7 @@ PLATFORM_JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xlog:gc*:gc.lo NETWORK_NODE_LOGS_ROOT_PATH=./network-logs/node APPLICATION_ROOT_PATH=./compose-network/network-node APPLICATION_CONFIG_PATH=./compose-network/network-node/data/config -RECORD_PARSER_ROOT_PATH=./src/record-parser +RECORD_PARSER_ROOT_PATH=./src/services/record-parser #### Network Node Memory Limits #### NETWORK_NODE_MEM_LIMIT=8gb @@ -78,6 +78,7 @@ RELAY_RATE_LIMIT_DISABLED=true #### Record Stream Uploader #### STREAM_EXTENSION=rcd.gz +STREAM_SIG_EXTENSION=rcd_sig #### ENVOY #### ENVOY_IMAGE_PREFIX=envoyproxy/ diff --git a/README.md b/README.md index f9e34409..99ab3aa5 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ Available commands: --balance to set starting hbar balance of the created accounts. --async to enable or disable asynchronous creation of accounts. --b or --blocklist to enable or disable account blocklisting. Depending on how many private keys are blocklisted, this will affect the generated on startup accounts. + -g, --enable-debug Enable or disable debugging of the local node [boolean] [default: false] stop - Stops the local hedera network and delete all the existing data. restart - Restart the local hedera network. generate-accounts - Generates N accounts, default 10. @@ -115,6 +116,9 @@ Available commands: --h or --host to override the default host. --balance to set starting hbar balance of the created accounts. --async to enable or disable asynchronous creation of accounts. + debug [timestamp] - Parses and prints the contents of the record file that has been created + during the selected timestamp. + Important: Local node must be started with the -g, --enable-debug flag to enable this feature ``` Note: Generated accounts are 3 types (ECDSA, Alias ECDSA and ED25519). All of them are usable via HederaSDK. Only Alias ECDSA accounts can be imported into wallet like Metamask or used in ethers. diff --git a/src/data/StateData.ts b/src/data/StateData.ts index 0dd21c20..dd865d0a 100644 --- a/src/data/StateData.ts +++ b/src/data/StateData.ts @@ -26,6 +26,7 @@ import { NetworkPrepState } from '../state/NetworkPrepState'; import { StartState } from '../state/StartState'; import { StopState } from '../state/StopState'; import { StateConfiguration } from '../types/StateConfiguration'; +import { DebugState } from '../state/DebugState'; export class StateData { @@ -39,6 +40,8 @@ export class StateData { return this.getStopConfiguration(); case 'accountCreation': return this.getAccountCreationConfiguration(); + case 'debug': + return this.getDebugConfiguration(); default: return undefined; } @@ -92,4 +95,13 @@ export class StateData { ] } } + + private getDebugConfiguration(): StateConfiguration { + return { + 'stateMachineName' : 'debug', + 'states' : [ + new DebugState() + ] + } + } } diff --git a/src/services/CLIService.ts b/src/services/CLIService.ts index 6ac501c0..cae581da 100644 --- a/src/services/CLIService.ts +++ b/src/services/CLIService.ts @@ -54,6 +54,7 @@ export class CLIService implements IService{ this.userComposeOption(yargs); this.userComposeDirOption(yargs); this.blocklistingOption(yargs); + this.enableDebugOption(yargs); this.isStartup = true; } @@ -72,20 +73,22 @@ export class CLIService implements IService{ public getCurrentArgv(){ const argv = this.currentArgv as ArgumentsCamelCase<{}>; - const accounts: number = argv.accounts as number; - const async: any = argv.async as boolean; - const balance: number = argv.balance as number; - const detached: boolean = this.isStartup ? argv.detached as boolean : true; - const host: string = argv.host as string; - const network: NetworkType = this.resolveNetwork(argv.network as string); - const limits: boolean = argv.limits as boolean; - const devMode: boolean = argv.dev as boolean; - const fullMode: boolean = argv.full as boolean; - const multiNode: boolean = argv.multinode as boolean; - const userCompose: boolean = argv.usercompose as boolean; - const userComposeDir: string = argv.composedir as string; - const blocklisting: boolean = argv.blocklist as boolean; - const startup: boolean = this.isStartup; + const accounts = argv.accounts as number; + const async = argv.async as boolean; + const balance = argv.balance as number; + const detached = this.isStartup ? argv.detached as boolean : true; + const host = argv.host as string; + const network = this.resolveNetwork(argv.network as string); + const limits = argv.limits as boolean; + const devMode = argv.dev as boolean; + const fullMode = argv.full as boolean; + const multiNode = argv.multinode as boolean; + const userCompose = argv.usercompose as boolean; + const userComposeDir = argv.composedir as string; + const blocklisting = argv.blocklist as boolean; + const timestamp = argv.timestamp as string; + const enableDebug = argv.enableDebug as boolean; + const startup = this.isStartup; const currentArgv: CLIOptions = { accounts, @@ -101,7 +104,9 @@ export class CLIService implements IService{ userCompose, userComposeDir, blocklisting, - startup + startup, + timestamp, + enableDebug }; return currentArgv; @@ -240,6 +245,16 @@ export class CLIService implements IService{ default: false }); } + + private enableDebugOption(yargs: Argv<{}>): void { + yargs.option('enable-debug', { + alias: 'g', + type: 'boolean', + describe: 'Enable or disable debugging of the local node', + demandOption: false, + default: false + }); + } private resolveNetwork(network: string): NetworkType { switch (network) { diff --git a/src/services/record-parser/src/Parser.java b/src/services/record-parser/src/Parser.java index ae798a9e..5866a003 100644 --- a/src/services/record-parser/src/Parser.java +++ b/src/services/record-parser/src/Parser.java @@ -1,4 +1,4 @@ -import static com.hedera.services.utils.forensics.RecordParsers.parseV6RecordStreamEntriesIn; +import static com.hedera.node.app.service.mono.utils.forensics.RecordParsers.parseV6RecordStreamEntriesIn; public class Parser { public static void main(String[] args) { try { diff --git a/src/state/DebugState.ts b/src/state/DebugState.ts index 06a1a213..ac860723 100644 --- a/src/state/DebugState.ts +++ b/src/state/DebugState.ts @@ -18,10 +18,20 @@ * */ +import { resolve } from 'path'; +import { readdirSync, copyFileSync, unlinkSync } from 'fs'; +import shell from 'shelljs'; import { IOBserver } from '../controller/IObserver'; import { LoggerService } from '../services/LoggerService'; import { ServiceLocator } from '../services/ServiceLocator'; import { IState } from './IState'; +import { CLIService } from '../services/CLIService'; + +const RELATIVE_TMP_DIR_PATH = '../../src/services/record-parser/temp'; +const RELATIVE_RECORDS_DIR_PATH = '../../network-logs/node/recordStreams/record0.0.3'; +const NO_RECORD_FILE_FOUND_ERROR = 'No record file found for the given timestamp.'; +const INVALID_TIMESTAMP_ERROR = 'Invalid timestamp string. Accepted formats are: 0000000000.000000000 and 0000000000-000000000' +const DEBUG_MODE_ERROR = 'Debug mode is not enabled to use this command. Please use the --enable-debug flag to enable it.'; export class DebugState implements IState{ private logger: LoggerService; @@ -41,6 +51,81 @@ export class DebugState implements IState{ } public async onStart(): Promise { - throw new Error('Method not implemented.'); + try { + const { timestamp, devMode } = ServiceLocator.Current.get(CLIService.name).getCurrentArgv(); + DebugState.checkForDebugMode(devMode); + this.logger.trace('Debug State Starting...', this.stateName); + const jsTimestampNum = DebugState.getAndValidateTimestamp(timestamp) + + const tempDir = resolve(__dirname, RELATIVE_TMP_DIR_PATH); + const recordFilesDirPath = resolve(__dirname, RELATIVE_RECORDS_DIR_PATH); + this.findAndCopyRecordFileToTmpDir(jsTimestampNum, recordFilesDirPath, tempDir) + // Perform the parsing + await shell.exec( + 'docker exec network-node bash /opt/hgcapp/recordParser/parse.sh' + ); + + DebugState.cleanTempDir(tempDir); + } catch (error: any) { + this.logger.error(error.message); + return + } + } + + private static cleanTempDir(dirPath: string): void { + for (const tempFile of readdirSync(dirPath)) { + if (tempFile !== '.gitignore') { + unlinkSync(resolve(dirPath, tempFile)); + } + } + } + + private static checkForDebugMode(debugMode: boolean): void { + if (!debugMode) { + throw new Error(DEBUG_MODE_ERROR); + } + } + + private static getAndValidateTimestamp(timestamp: string): number { + const timestampRegEx = /^\d{10}[.-]\d{9}$/; + if (!timestampRegEx.test(timestamp)) { + throw new Error(INVALID_TIMESTAMP_ERROR); + } + + // Parse the timestamp to a record file filename + let jsTimestamp = timestamp + .replace('.', '') + .replace('-', '') + .substring(0, 13); + return parseInt(jsTimestamp); + } + + private findAndCopyRecordFileToTmpDir(jsTimestampNum: number, recordFilesDirPath: string, tmpDirPath: string): void { + // Copy the record file to a temp directory + const files = readdirSync(recordFilesDirPath); + const recordExt = `.${process.env.STREAM_EXTENSION}`; + for (const file of files) { + const recordFileName = file.replace(recordExt, ''); + const fileTimestamp = new Date(recordFileName.replace(/_/g, ':')).getTime(); + if (fileTimestamp >= jsTimestampNum) { + if (file.endsWith(recordExt)) { + this.logger.trace(`Parsing record file [${file}]\n`); + } + + const sigFile = recordFileName + `.${process.env.STREAM_SIG_EXTENSION}`; + copyFileSync( + resolve(recordFilesDirPath, file), + resolve(tmpDirPath, file) + ); + copyFileSync( + resolve(recordFilesDirPath, sigFile), + resolve(tmpDirPath, sigFile) + ); + + return + } + } + + throw new Error(NO_RECORD_FILE_FOUND_ERROR); } } diff --git a/src/state/InitState.ts b/src/state/InitState.ts index ceb4edd6..2abd10d7 100644 --- a/src/state/InitState.ts +++ b/src/state/InitState.ts @@ -133,6 +133,7 @@ export class InitState implements IState{ private configureMirrorNodeProperties() { this.logger.trace('Configuring required mirror node properties, depending on selected configuration...', this.stateName); const turboMode = !this.cliOptions.fullMode; + const debugMode = this.cliOptions.enableDebug; // const multiNode = this.cliOptions.multiNode; @@ -144,6 +145,10 @@ export class InitState implements IState{ application.hedera.mirror.importer.downloader.sources = originalNodeConfiguration.turboNodeProperties.sources; } + if(debugMode) { + application.hedera.mirror.importer.downloader.local.deleteAfterProcessing = false + } + // if (multiNode) { // application['hedera']['mirror']['monitor']['nodes'] = originalNodeConfiguration.multiNodeProperties // } diff --git a/src/state/StartState.ts b/src/state/StartState.ts index 5276daf7..732a550c 100644 --- a/src/state/StartState.ts +++ b/src/state/StartState.ts @@ -105,7 +105,7 @@ export class StartState implements IState{ } return shell.exec( - `docker compose -f ${composeFiles.join(' -f ')} up -d 2>${this.dockerService.getNullOutput()}` + `docker compose -f ${composeFiles.join(' -f ')} up -d 2>${this.dockerService.getNullOutput()}` ); } diff --git a/src/types/CLIOptions.ts b/src/types/CLIOptions.ts index 364d763e..85112128 100644 --- a/src/types/CLIOptions.ts +++ b/src/types/CLIOptions.ts @@ -35,4 +35,6 @@ export interface CLIOptions { userComposeDir: string, blocklisting: boolean, startup: boolean, + timestamp: string, + enableDebug: boolean }