Skip to content

Commit

Permalink
feat: allow setting of working directory (#473)
Browse files Browse the repository at this point in the history
Signed-off-by: Iliya Savov <isavov@users.noreply.github.com>
  • Loading branch information
isavov authored Jan 8, 2024
1 parent 4f30b52 commit 221941d
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 35 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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/services/record-parser
MIRROR_NODE_CONFIG_PATH=.

#### Network Node Memory Limits ####
NETWORK_NODE_MEM_LIMIT=8gb
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Available commands:
--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.
--enable-debug Enable or disable debugging of the local node [boolean] [default: false]
--workdir Path to the working directory for local node [string] [default: "[USER APP DATA]/hedera-local"]
stop - Stops the local hedera network and delete all the existing data.
restart - Restart the local hedera network.
generate-accounts <n> - Generates N accounts, default 10.
Expand Down Expand Up @@ -539,7 +540,15 @@ These are the local network variables to interact with the consensus and mirror
1. `compose-network` folder has the static files needed for starting Local network.
2. `compose-network/grafana/dashboards` folder contains the Grafana dashboard definitions in JSON format which will be automatically provisioned at startup.
3. `compose-network/grafana/datasources` folder contains the Grafana datasource definitions in YAML format which wil be automatically provisioned at startup.
4. `network-logs` folder will be created at runtime and will have all the log files generated after starting local node.
4. `network-logs` folder will be created at runtime in the working directory and will have all the log files generated after starting local node.
The local node writes its ephemeral data to a `working directory` which can be set using the `--workdir` flag, and has a default value dependant on the OS of the user
| OS | Default Working Directory |
|---------|----------------------------------------------|
| MacOS | `~/Library/Application Support/hedera-local` |
| Linux | `~/.local/share/hedera-local` |
| Windows | `%USERPROFILE%\AppData\Local\hedera-local` |
## Steps to change the memory limits, properties and other configurations
Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ services:
ports:
- "5600:5600"
volumes:
- ./compose-network/mirror-node/application.yml:/usr/etc/hedera-mirror-grpc/application.yml
- "${MIRROR_NODE_CONFIG_PATH}/compose-network/mirror-node/application.yml:/usr/etc/hedera-mirror-grpc/application.yml"

importer:
image: "${MIRROR_IMAGE_PREFIX}hedera-mirror-importer:${MIRROR_IMAGE_TAG}"
Expand All @@ -255,7 +255,7 @@ services:
- cloud-storage
- mirror-node
volumes:
- ./compose-network/mirror-node/application.yml:/usr/etc/hedera-mirror-importer/application.yml
- "${MIRROR_NODE_CONFIG_PATH}/compose-network/mirror-node/application.yml:/usr/etc/hedera-mirror-importer/application.yml"
- ./compose-network/mirror-node/addressBook.bin:/usr/etc/hedera-mirror-importer/local-dev-1-node.addressbook.f102.json.bin

rest:
Expand Down Expand Up @@ -332,7 +332,7 @@ services:
restart: unless-stopped
tty: true
volumes:
- ./compose-network/mirror-node/application.yml:/usr/etc/hedera-mirror-monitor/application.yml
- "${MIRROR_NODE_CONFIG_PATH}/compose-network/mirror-node/application.yml:/usr/etc/hedera-mirror-monitor/application.yml"

relay:
image: "${RELAY_IMAGE_PREFIX}hedera-json-rpc-relay:${RELAY_IMAGE_TAG}"
Expand Down
4 changes: 2 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ export const UNKNOWN_VERSION = "Unknown";
export const NECESSARY_PORTS = [5551, 8545, 5600, 5433, 50211, 8082];
export const OPTIONAL_PORTS = [7546, 8080, 6379, 3000];
export const EVM_ADDRESSES_BLOCKLIST_FILE_RELATIVE_PATH = '../../compose-network/network-node'
export const RELATIVE_TMP_DIR_PATH = '../../src/services/record-parser/temp';
export const RELATIVE_RECORDS_DIR_PATH = '../../network-logs/node/recordStreams/record0.0.3';
export const RELATIVE_TMP_DIR_PATH = 'services/record-parser/temp';
export const RELATIVE_RECORDS_DIR_PATH = 'network-logs/node/recordStreams/record0.0.3';
export const APPLICATION_YML_RELATIVE_PATH = '../../compose-network/mirror-node/application.yml';
45 changes: 29 additions & 16 deletions src/services/CLIService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { NetworkType } from '../types/NetworkType';
import { VerboseLevel } from '../types/VerboseLevel';
import { LoggerService } from './LoggerService';
import { ServiceLocator } from './ServiceLocator';
import { FileSystemUtils } from '../utils/FileSystemUtils';

export class CLIService implements IService{
private logger: LoggerService;
Expand Down Expand Up @@ -79,9 +80,10 @@ export class CLIService implements IService{

private static loadCommonOptions(yargs: Argv<{}>): void {
CLIService.verboseLevelOption(yargs);
CLIService.workDirOption(yargs);
}

public getCurrentArgv(){
public getCurrentArgv() {
const argv = this.currentArgv as ArgumentsCamelCase<{}>;
const accounts = argv.accounts as number;
const async = argv.async as boolean;
Expand All @@ -100,6 +102,7 @@ export class CLIService implements IService{
const verbose = CLIService.resolveVerboseLevel(argv.verbose as string);
const timestamp = argv.timestamp as string;
const enableDebug = argv.enableDebug as boolean;
const workDir = FileSystemUtils.parseWorkDir(argv.workdir as string);

const currentArgv: CLIOptions = {
accounts,
Expand All @@ -118,7 +121,8 @@ export class CLIService implements IService{
startup,
verbose,
timestamp,
enableDebug
enableDebug,
workDir,
};

return currentArgv;
Expand Down Expand Up @@ -164,7 +168,7 @@ export class CLIService implements IService{
describe: 'Run the local node in detached mode',
demandOption: false,
default: false
});
});
}

private static hostOption(yargs: Argv<{}>): void {
Expand All @@ -174,18 +178,18 @@ export class CLIService implements IService{
describe: 'Run the local node with host',
demandOption: false,
default: '127.0.0.1'
});
});
}

private static networkOption(yargs: Argv<{}>): void {
yargs.option('network', {
alias: 'n',
type: 'string',
describe:
"Select the network configuration. Pre-built configs: ['mainnet', 'previewnet', 'testnet', 'local']",
"Select the network configuration. Pre-built configs: ['mainnet', 'previewnet', 'testnet', 'local']",
demandOption: false,
default: 'local'
});
});
}

private static rateLimitOption(yargs: Argv<{}>): void {
Expand All @@ -195,15 +199,15 @@ export class CLIService implements IService{
describe: 'Enable or disable the rate limits in the JSON-RPC relay',
demandOption: false,
default: false
});
});
}

private static timestampOption(yargs: Argv<{}>): void {
yargs.option('timestamp', {
type: 'string',
describe: 'Record file timestamp',
demandOption: true
});
});
}

private static devModeOption(yargs: Argv<{}>): void {
Expand All @@ -212,7 +216,7 @@ export class CLIService implements IService{
describe: 'Enable or disable developer mode',
demandOption: false,
default: false
});
});
}

private static fullModeOption(yargs: Argv<{}>): void {
Expand All @@ -221,7 +225,7 @@ export class CLIService implements IService{
describe: 'Enable or disable full mode. Production local-node.',
demandOption: false,
default: false
});
});
}

private static multiNodeOption(yargs: Argv<{}>): void {
Expand All @@ -230,7 +234,7 @@ export class CLIService implements IService{
describe: 'Enable or disable multi-node mode.',
demandOption: false,
default: false
});
});
}

private static balanceOption(yargs: Argv<{}>): void {
Expand All @@ -239,7 +243,7 @@ export class CLIService implements IService{
describe: 'Set starting balance of the created accounts in HBAR',
demandOption: false,
default: 10000
});
});
}

private static asyncOption(yargs: Argv<{}>): void {
Expand All @@ -249,7 +253,7 @@ export class CLIService implements IService{
describe: 'Enable or disable asynchronous creation of accounts',
demandOption: false,
default: false
});
});
}

private static userComposeOption(yargs: Argv<{}>): void {
Expand All @@ -258,7 +262,7 @@ export class CLIService implements IService{
describe: 'Enable or disable user Compose configuration files',
demandOption: false,
default: true
});
});
}

private static userComposeDirOption(yargs: Argv<{}>): void {
Expand All @@ -267,7 +271,16 @@ export class CLIService implements IService{
describe: 'Path to a directory with user Compose configuration files',
demandOption: false,
default: './overrides/'
});
});
}

private static workDirOption(yargs: Argv<{}>): void {
yargs.option('workdir', {
type: 'string',
describe: 'Path to the working directory for local node',
demandOption: false,
default: FileSystemUtils.getPlatformSpecificAppDataPath('hedera-local')
});
}

private static blocklistingOption(yargs: Argv<{}>): void {
Expand All @@ -277,7 +290,7 @@ export class CLIService implements IService{
describe: 'Enable or disable blocklisting accounts',
demandOption: false,
default: false
});
});
}

public static verboseLevelOption(yargs: Argv<{}>): void {
Expand Down
20 changes: 16 additions & 4 deletions src/state/CleanUpState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,30 @@
*
*/

import { readFileSync, writeFileSync } from 'fs';
import { readFileSync, writeFileSync, existsSync } from 'fs';
import yaml from 'js-yaml';
import { join } from 'path';
import { IOBserver } from '../controller/IObserver';
import originalNodeConfiguration from '../configuration/originalNodeConfiguration.json';
import { LoggerService } from '../services/LoggerService';
import { ServiceLocator } from '../services/ServiceLocator';
import { IState } from './IState';
import { CLIService } from '../services/CLIService';
import { CLIOptions } from '../types/CLIOptions';
import { EventType } from '../types/EventType';

export class CleanUpState implements IState{
private logger: LoggerService;

private observer: IOBserver | undefined;

private cliOptions: CLIOptions;

private stateName: string;

constructor() {
this.stateName = CleanUpState.name;
this.cliOptions = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv();
this.logger = ServiceLocator.Current.get<LoggerService>(LoggerService.name);
this.logger.trace('Clean Up State Initialized!', this.stateName);
}
Expand All @@ -56,7 +61,11 @@ export class CleanUpState implements IState{

private revertMirrorNodeProperties() {
this.logger.trace('Clean up unneeded mirror node properties...', this.stateName);
const propertiesFilePath = join(__dirname, '../../compose-network/mirror-node/application.yml');
const propertiesFilePath = join(this.cliOptions.workDir, 'compose-network/mirror-node/application.yml');
if (!existsSync(propertiesFilePath)) {
this.logger.trace(`Mirror Node Properties File doesn't exist at path ${propertiesFilePath}`,this.stateName);
return;
}
const application = yaml.load(readFileSync(propertiesFilePath).toString()) as any;
delete application.hedera.mirror.importer.dataPath;
delete application.hedera.mirror.importer.downloader.sources;
Expand All @@ -69,8 +78,11 @@ export class CleanUpState implements IState{

private revertNodeProperties(): void {
this.logger.trace('Clean up unneeded bootstrap properties.', this.stateName);
const propertiesFilePath = join(__dirname, '../../compose-network/network-node/data/config/bootstrap.properties');

const propertiesFilePath = join(this.cliOptions.workDir, 'compose-network/network-node/data/config/bootstrap.properties');
if (!existsSync(propertiesFilePath)) {
this.logger.trace(`Node Properties File doesn't exist at path ${propertiesFilePath}`,this.stateName);
return;
}
let originalProperties = '';
originalNodeConfiguration.bootsrapProperties.forEach(property => {
originalProperties = originalProperties.concat(`${property.key}=${property.value}\n`);
Expand Down
6 changes: 3 additions & 3 deletions src/state/DebugState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ export class DebugState implements IState{

public async onStart(): Promise<void> {
try {
const { timestamp } = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv();
const { timestamp, workDir } = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv();
// DebugState.checkForDebugMode();
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);
const tempDir = resolve(workDir, RELATIVE_TMP_DIR_PATH);
const recordFilesDirPath = resolve(workDir, RELATIVE_RECORDS_DIR_PATH);
this.findAndCopyRecordFileToTmpDir(jsTimestampNum, recordFilesDirPath, tempDir)
// Perform the parsing
await shell.exec(
Expand Down
31 changes: 28 additions & 3 deletions src/state/InitState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import { ServiceLocator } from '../services/ServiceLocator';
import { IState } from './IState';
import { CLIService } from '../services/CLIService';
import { CLIOptions } from '../types/CLIOptions';
import { FileSystemUtils } from '../utils/FileSystemUtils';
import { IOBserver } from '../controller/IObserver';
import { EventType } from '../types/EventType';
import { ConfigurationData } from '../data/ConfigurationData';
import { Configuration } from '../types/NetworkConfiguration';
import originalNodeConfiguration from '../configuration/originalNodeConfiguration.json';
import { DockerService } from '../services/DockerService';
import { NECESSARY_PORTS, OPTIONAL_PORTS } from '../constants';
import { APPLICATION_YML_RELATIVE_PATH, NECESSARY_PORTS, OPTIONAL_PORTS } from '../constants';

configDotenv({ path: path.resolve(__dirname, '../../.env') });

Expand Down Expand Up @@ -75,13 +76,37 @@ export class InitState implements IState{

this.logger.info(`Setting configuration for ${this.cliOptions.network} network with latest images on host ${this.cliOptions.host} with dev mode turned ${this.cliOptions.devMode ? 'on' : 'off'} using ${this.cliOptions.fullMode? 'full': 'turbo'} mode in ${this.cliOptions.multiNode? 'multi' : 'single'} node configuration...`, this.stateName);

this.prepareWorkDirectory();
const workDirConfiguration = [
{ key: 'NETWORK_NODE_LOGS_ROOT_PATH', value: join(this.cliOptions.workDir, 'network-logs', 'node') },
{ key: 'APPLICATION_CONFIG_PATH', value: join(this.cliOptions.workDir, 'compose-network', 'network-node', 'data', 'config') },
{ key: 'MIRROR_NODE_CONFIG_PATH', value: this.cliOptions.workDir },
{ key: 'RECORD_PARSER_ROOT_PATH', value: join(this.cliOptions.workDir, 'services','record-parser') },
];
configurationData.envConfiguration = (configurationData.envConfiguration ?? []).concat(workDirConfiguration);

this.configureEnvVariables(configurationData.imageTagConfiguration, configurationData.envConfiguration);
this.configureNodeProperties(configurationData.nodeConfiguration?.properties);
this.configureMirrorNodeProperties();

this.observer!.update(EventType.Finish);
}

private prepareWorkDirectory() {
this.logger.info(`Local Node Working directory set to ${this.cliOptions.workDir}`, this.stateName);
FileSystemUtils.createEphemeralDirectories(this.cliOptions.workDir);
const configDirSource = join(__dirname, '../../compose-network/network-node/data/config/');
const configPathMirrorNodeSource = join(__dirname, APPLICATION_YML_RELATIVE_PATH);
const recordParserSource = join(__dirname,'../../src/services/record-parser');

const configFiles = {
[configDirSource]: `${this.cliOptions.workDir}/compose-network/network-node/data/config`,
[configPathMirrorNodeSource]: `${this.cliOptions.workDir}/compose-network/mirror-node/application.yml`,
[recordParserSource]: `${this.cliOptions.workDir}/services/record-parser`
};
FileSystemUtils.copyPaths(configFiles);
}

private configureEnvVariables(imageTagConfiguration: Array<Configuration>, envConfiguration: Array<Configuration> | undefined): void {
imageTagConfiguration.forEach(variable => {
process.env[variable.key] = variable.value;
Expand Down Expand Up @@ -109,7 +134,7 @@ export class InitState implements IState{
}

private configureNodeProperties(nodeConfiguration: Array<Configuration> | undefined): void {
const propertiesFilePath = join(__dirname, '../../compose-network/network-node/data/config/bootstrap.properties');
const propertiesFilePath = join(this.cliOptions.workDir, 'compose-network/network-node/data/config/bootstrap.properties');

let newProperties = '';
originalNodeConfiguration.bootsrapProperties.forEach(property => {
Expand Down Expand Up @@ -137,7 +162,7 @@ export class InitState implements IState{

const multiNode = this.cliOptions.multiNode;

const propertiesFilePath = join(__dirname, '../../compose-network/mirror-node/application.yml');
const propertiesFilePath = join(this.cliOptions.workDir, 'compose-network/mirror-node/application.yml');
const application = yaml.load(readFileSync(propertiesFilePath).toString()) as any;

if (turboMode) {
Expand Down
Loading

0 comments on commit 221941d

Please sign in to comment.