From e71d7af9d0b7f89ba16b0f4947bca7488c363ac1 Mon Sep 17 00:00:00 2001 From: kidneyweak Date: Mon, 29 Jan 2024 11:29:53 +0800 Subject: [PATCH] feat: optimize create network process --- src/quorum/command/network/create.ts | 9 +- src/quorum/command/network/generate.ts | 9 +- src/quorum/instance/bdkFile.ts | 12 +- src/quorum/model/type/network.type.ts | 5 + src/quorum/service/network.ts | 156 ++++++++++++++----------- src/wallet/service/wallet.ts | 1 - 6 files changed, 116 insertions(+), 76 deletions(-) diff --git a/src/quorum/command/network/create.ts b/src/quorum/command/network/create.ts index b9947cec..5b8d34c9 100644 --- a/src/quorum/command/network/create.ts +++ b/src/quorum/command/network/create.ts @@ -2,6 +2,7 @@ import { ethers } from 'ethers' import prompts from 'prompts' import { Argv, Arguments } from 'yargs' import Network from '../../service/network' +import Backup from '../../service/backup' import { onCancel } from '../../../util/error' import { NetworkCreateType } from '../../model/type/network.type' import config from '../../config' @@ -26,11 +27,12 @@ export const builder = (yargs: Argv) => { export const handler = async (argv: Arguments) => { const network = new Network(config) + const backup = new Backup(config) const wallet = new Wallet() // check bdkPath files exist or not (include useless file e.g. .DS_Store) const confirm: boolean = await (async () => { network.createBdkFolder() - const fileList = network.getBdkFiles() + const fileList = network.getNetworkFiles() if (fileList.length !== 0) { const confirmDelete = (await prompts({ type: 'confirm', @@ -40,8 +42,11 @@ export const handler = async (argv: Arguments) => { }, { onCancel })).value if (confirmDelete) { const spinner = ora('Quorum Network Create ...').start() + // backup before remove + await backup.exportAll() + network.removeBdkFiles(fileList) - spinner.succeed('Remove all existing files!') + spinner.succeed('Backup and remove all existing files!') } return confirmDelete } else { diff --git a/src/quorum/command/network/generate.ts b/src/quorum/command/network/generate.ts index 20caf867..4f003e66 100644 --- a/src/quorum/command/network/generate.ts +++ b/src/quorum/command/network/generate.ts @@ -1,6 +1,7 @@ import prompts from 'prompts' import { Argv, Arguments } from 'yargs' import Network from '../../service/network' +import Backup from '../../service/backup' import { onCancel, ParamsError } from '../../../util/error' import { NetworkGenerateType } from '../../model/type/network.type' import config from '../../config' @@ -22,6 +23,7 @@ export const builder = (yargs: Argv) => { export const handler = async (argv: Arguments) => { const network = new Network(config) + const backup = new Backup(config) // check bdkPath files exist or not (include useless file e.g. .DS_Store) const confirm: boolean = await (async () => { network.createBdkFolder() @@ -34,9 +36,12 @@ export const handler = async (argv: Arguments) => { initial: false, }, { onCancel })).value if (confirmDelete) { - const spinner = ora('Quorum Network Generate ...').start() + const spinner = ora('Quorum Network Create ...').start() + // backup before remove + await backup.exportAll() + network.removeBdkFiles(fileList) - spinner.succeed('Remove all existing files!') + spinner.succeed('Backup and remove all existing files!') } return confirmDelete } else { diff --git a/src/quorum/instance/bdkFile.ts b/src/quorum/instance/bdkFile.ts index 0d01f31c..1978c52b 100644 --- a/src/quorum/instance/bdkFile.ts +++ b/src/quorum/instance/bdkFile.ts @@ -1,7 +1,7 @@ import ExplorerDockerComposeYaml from '../model/yaml/docker-compose/explorerDockerComposeYaml' import fs from 'fs-extra' import { Config } from '../config' -import { GenesisJsonType } from '../model/type/network.type' +import { GenesisJsonType, NetworkInfoItem } from '../model/type/network.type' import ValidatorDockerComposeYaml from '../model/yaml/docker-compose/validatorDockerComposeYaml' import MemberDockerComposeYaml from '../model/yaml/docker-compose/memberDockerCompose' import { PathError } from '../../util/error' @@ -50,6 +50,10 @@ export default class BdkFile { fs.writeFileSync(`${this.bdkPath}/artifacts/goQuorum/static-nodes.json`, JSON.stringify(staticNodesJson, null, 2)) } + public createNetworkInfoJson (networkInfoJson: Array) { + fs.writeFileSync(`${this.bdkPath}/network-info.json`, JSON.stringify(networkInfoJson, null, 2)) + } + public createPrivateKey (dir: string, privateKey: string) { fs.mkdirSync(`${this.bdkPath}/${dir}`, { recursive: true }) fs.writeFileSync(`${this.bdkPath}/${dir}/nodekey`, privateKey) @@ -217,6 +221,12 @@ export default class BdkFile { return JSON.parse(staticNodesJson) } + public getNetworkInfoJson () { + this.checkPathExist(this.bdkPath) + const networkInfoJson = fs.readFileSync(`${this.bdkPath}/network-info.json`, 'utf8') + return JSON.parse(networkInfoJson) + } + public getPermissionedNodesJson () { this.checkPathExist(this.bdkPath) const permissionedNodesJson = fs.readFileSync(`${this.bdkPath}/artifacts/goQuorum/permissioned-nodes.json`, 'utf8') diff --git a/src/quorum/model/type/network.type.ts b/src/quorum/model/type/network.type.ts index 8732d653..d5c9213c 100644 --- a/src/quorum/model/type/network.type.ts +++ b/src/quorum/model/type/network.type.ts @@ -98,3 +98,8 @@ export interface AddMemberRemoteType { discoveryPort: string ipAddress: string } + +export interface NetworkInfoItem { + label: string + value: string +} diff --git a/src/quorum/service/network.ts b/src/quorum/service/network.ts index 6d92eaa4..954bf8d0 100644 --- a/src/quorum/service/network.ts +++ b/src/quorum/service/network.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers' import RLP from 'rlp' -import { NetworkCreateType, NetworkGenerateType, JoinNodeType, AddValidatorRemoteType, AddMemberRemoteType } from '../model/type/network.type' +import { NetworkCreateType, NetworkGenerateType, JoinNodeType, AddValidatorRemoteType, AddMemberRemoteType, NetworkInfoItem } from '../model/type/network.type' import GenesisJsonYaml from '../model/yaml/network/gensisJson' import { AbstractService } from './Service.abstract' import ValidatorInstance from '../instance/validator' @@ -17,6 +17,7 @@ export default class Network extends AbstractService { */ public async create (networkCreateConfig: NetworkCreateType) { const validatorAddressList: Buffer[] = [] + const networkInfo: NetworkInfoItem[] = [] for (let i = 0; i < networkCreateConfig.validatorNumber; i += 1) { const { address } = this.createKey(`artifacts/validator${i}`) validatorAddressList.push(Buffer.from(address, 'hex')) @@ -44,7 +45,7 @@ export default class Network extends AbstractService { this.bdkFile.createGenesisJson(genesisJson) this.bdkFile.createDisallowedNodesJson([]) - const staticNodesJson = [] + const staticNodesJson: string[] = [] const bdkPath = this.bdkFile.getBdkPath() for (let i = 0; i < networkCreateConfig.validatorNumber; i += 1) { @@ -73,6 +74,7 @@ export default class Network extends AbstractService { this.bdkFile.copyAddressToValidator(i) validatorDockerComposeYaml.addValidator(bdkPath, i, 8545 + i * 2, networkCreateConfig.chainId, 30303 + i, networkCreateConfig.bootNodeList[i], staticNodesJson[i]) + this.createNetworkInfoJson(networkInfo, `http://validator${i}:${8545 + i * 2}`) } this.bdkFile.createValidatorDockerComposeYaml(validatorDockerComposeYaml) @@ -91,16 +93,19 @@ export default class Network extends AbstractService { this.bdkFile.copyAddressToMember(i) memberDockerComposeYaml.addMember(bdkPath, i, 8645 + i * 2, networkCreateConfig.chainId, 30403 + i, networkCreateConfig.bootNodeList[networkCreateConfig.validatorNumber + i], staticNodesJson[networkCreateConfig.validatorNumber + i]) + this.createNetworkInfoJson(networkInfo, `http://member${i}:${8645 + i * 2}`) } this.bdkFile.createMemberDockerComposeYaml(memberDockerComposeYaml) await (new MemberInstance(this.config, this.infra).up()) } + this.bdkFile.createNetworkInfoJson(networkInfo) } public async joinNode (joinNodeConfig: JoinNodeType) { + const networkInfo: NetworkInfoItem[] = this.bdkFile.getNetworkInfoJson() const nodeNum = Number(joinNodeConfig.node.replace(/(validator|member)+/g, '')) - const staticNodesJson = [] + const staticNodesJson: string[] = [] const bdkPath = this.bdkFile.getBdkPath() const enodeInfo = String(this.getNodeInfo(joinNodeConfig.node, 'enodeInfo')) const publicKey = String(this.getNodeInfo(joinNodeConfig.node, 'publicKey')) @@ -128,29 +133,14 @@ export default class Network extends AbstractService { // TODO: add bootnode selection validatorDockerComposeYaml.addValidator(bdkPath, nodeNum, 8545 + nodeNum * 2, joinNodeConfig.genesisJson.config.chainId, 30303 + nodeNum, false, '') + this.createNetworkInfoJson(networkInfo, `http://validator${nodeNum}:${8545 + nodeNum * 2}`) this.bdkFile.createValidatorDockerComposeYaml(validatorDockerComposeYaml) await (new ValidatorInstance(this.config, this.infra).upOneService(`${joinNodeConfig.node}`)) + this.bdkFile.createNetworkInfoJson(networkInfo) - let tryTime = 0 - while (await this.quorumCommand('istanbul.isValidator()', `${joinNodeConfig.node}`) !== 'true') { - if (tryTime !== 10) { - const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545') - const wallet = ethers.Wallet.createRandom().connect(provider) - const tx = { - to: '0x0000000000000000000000000000000000000000', - value: '0x0', - nonce: provider.getTransactionCount(wallet.getAddress(), 'latest'), - } - const receipt = await wallet.sendTransaction(tx) - await receipt.wait() + await this.istanbulIsValidator(joinNodeConfig.node) - tryTime += 1 - await sleep(500) - } else { - throw new TimeLimitError('[x] Time limit reached. Please check later.') - } - } return nodeNum } else { this.bdkFile.copyGenesisJsonToMember(nodeNum) @@ -160,9 +150,11 @@ export default class Network extends AbstractService { const memberDockerComposeYaml = new MemberDockerComposeYaml() // TODO: add bootnode selection memberDockerComposeYaml.addMember(bdkPath, nodeNum, 8645 + nodeNum * 2, joinNodeConfig.genesisJson.config.chainId, 30403 + nodeNum, false, '') + this.createNetworkInfoJson(networkInfo, `http://member${nodeNum}:${8645 + nodeNum * 2}`) this.bdkFile.createMemberDockerComposeYaml(memberDockerComposeYaml) await (new MemberInstance(this.config, this.infra).upOneService(`${joinNodeConfig.node}`)) + this.bdkFile.createNetworkInfoJson(networkInfo) let tryTime = 0 while (parseInt(await this.quorumCommand('net.peerCount', joinNodeConfig.node)) < 1) { @@ -230,7 +222,7 @@ export default class Network extends AbstractService { } public generate (networkGenerateConfig: NetworkGenerateType) { - const staticNodesJson = [] + const staticNodesJson: string[] = [] // Add node to static-nodes.json for (let i = 0; i < networkGenerateConfig.validatorNumber; i += 1) { @@ -259,6 +251,7 @@ export default class Network extends AbstractService { } public async addValidatorLocal () { + const networkInfo: NetworkInfoItem[] = await this.bdkFile.getNetworkInfoJson() // count validator number const validatorCount = parseInt(await this.quorumCommand('istanbul.getValidators().length', 'validator0')) const validatorNum = validatorCount @@ -290,8 +283,10 @@ export default class Network extends AbstractService { this.bdkFile.copyPermissionedNodesJsonToValidator(i) // TODO: add bootnode selection validatorDockerComposeYaml.addValidator(this.bdkFile.getBdkPath(), i, 8545 + i * 2, chainId, 30303 + i, false, '') + this.createNetworkInfoJson(networkInfo, `http://validator${i}:${8545 + i * 2}`) } this.bdkFile.createValidatorDockerComposeYaml(validatorDockerComposeYaml) + this.bdkFile.createNetworkInfoJson(networkInfo) // for loop to copy static-nodes.json to member const memberCount = await this.bdkFile.getExportFiles().filter(file => file.match(/(member)[0-9]+/g)).length @@ -301,30 +296,13 @@ export default class Network extends AbstractService { } await (new ValidatorInstance(this.config, this.infra).upOneService(`${newValidator}`)) + await this.istanbulIsValidator(newValidator) - let tryTime = 0 - while (await this.quorumCommand('istanbul.isValidator()', `${newValidator}`) !== 'true') { - if (tryTime !== 10) { - const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545') - const wallet = ethers.Wallet.createRandom().connect(provider) - const tx = { - to: '0x0000000000000000000000000000000000000000', - value: '0x0', - nonce: provider.getTransactionCount(wallet.getAddress(), 'latest'), - } - const receipt = await wallet.sendTransaction(tx) - await receipt.wait() - - tryTime += 1 - await sleep(500) - } else { - throw new TimeLimitError('[x] Time limit reached. Please check later.') - } - } return validatorNum } public async addMemberLocal () { + const networkInfo: NetworkInfoItem[] = await this.bdkFile.getNetworkInfoJson() // count member number const memberCount = (await this.bdkFile.getExportFiles().filter(file => file.match(/(member)[0-9]+/g))).length const memberNum = memberCount @@ -359,9 +337,10 @@ export default class Network extends AbstractService { this.bdkFile.copyPermissionedNodesJsonToMember(i) // TODO: add bootnode selection memberDockerComposeYaml.addMember(this.bdkFile.getBdkPath(), i, 8645 + i * 2, chainId, 30403 + i, false, '') + this.createNetworkInfoJson(networkInfo, `http://member${i}:${8645 + i * 2}`) } this.bdkFile.createMemberDockerComposeYaml(memberDockerComposeYaml) - + this.bdkFile.createNetworkInfoJson(networkInfo) await (new MemberInstance(this.config, this.infra).upOneService(`${newMember}`)) let tryTime = 0 @@ -377,29 +356,6 @@ export default class Network extends AbstractService { return memberNum } - public async upService (service: string) { - if (service.match(/validator[\w-]+/g)) { - await (new ValidatorInstance(this.config, this.infra).upOneService(service)) - } else if (service.match(/member[\w-]+/g)) { - await (new MemberInstance(this.config, this.infra).upOneService(service)) - } - } - - public async upAll () { - await (new ValidatorInstance(this.config, this.infra).up()) - await (new MemberInstance(this.config, this.infra).up()) - } - - public async down () { - await (new ValidatorInstance(this.config, this.infra).down()) - await (new MemberInstance(this.config, this.infra).down()) - } - - public async delete () { - await this.down() - this.removeBdkFiles(this.getNetworkFiles()) - } - public async checkNode (node: string, method: string) { const result = await this.quorumCommand(method, node) @@ -469,6 +425,32 @@ export default class Network extends AbstractService { return result } + /** + * @description network service + */ + public async upService (service: string) { + if (service.match(/validator[\w-]+/g)) { + await (new ValidatorInstance(this.config, this.infra).upOneService(service)) + } else if (service.match(/member[\w-]+/g)) { + await (new MemberInstance(this.config, this.infra).upOneService(service)) + } + } + + public async upAll () { + await (new ValidatorInstance(this.config, this.infra).up()) + await (new MemberInstance(this.config, this.infra).up()) + } + + public async down () { + await (new ValidatorInstance(this.config, this.infra).down()) + await (new MemberInstance(this.config, this.infra).down()) + } + + public async delete () { + await this.down() + this.removeBdkFiles(this.getNetworkFiles()) + } + /** @ignore */ private async quorumCommand (args: string, option: string) { const result = await this.infra.runCommand({ @@ -505,6 +487,42 @@ export default class Network extends AbstractService { return { privateKey, publicKey, address } } + /** @ignore */ + private createNetworkInfoJson (networkInfo: NetworkInfoItem[], endpoint: string) { + const match = endpoint.match(/((http[s]?):\/\/((validator|member)\d+)):(\d+)/) + if (!match) { + throw new Error('Invalid URL format') + } + const label = match[3] + const protocol = match[2] + const host = label.startsWith('validator') || label.startsWith('member') ? 'localhost' : label + const value = `${protocol}://${host}:${match[5]}` + networkInfo.push({ label, value }) + } + + /** @ignore */ + private async istanbulIsValidator (joinNode: string, retryTime = 10) { + let tryTime = 0 + while (await this.quorumCommand('istanbul.isValidator()', `${joinNode}`) !== 'true') { + if (tryTime !== retryTime) { + const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545') + const wallet = ethers.Wallet.createRandom().connect(provider) + const tx = { + to: '0x0000000000000000000000000000000000000000', + value: '0x0', + nonce: provider.getTransactionCount(wallet.getAddress(), 'latest'), + } + const receipt = await wallet.sendTransaction(tx) + await receipt.wait() + + tryTime += 1 + await sleep(500) + } else { + throw new TimeLimitError('[x] Time limit reached. Please check later.') + } + } + } + /** @ignore */ public createBdkFolder () { return this.bdkFile.createBdkFolder() @@ -517,11 +535,9 @@ export default class Network extends AbstractService { /** @ignore */ public getNetworkFiles () { - const array = [] - array.push(this.bdkFile.getExportFiles().filter(file => file.match(/(validator|member)+/g))) - array.push(this.bdkFile.getExportFiles().filter(file => file.match(/(artifacts)/g))) - const networkFilesList = array.flat() - + const networkFilesList = this.bdkFile.getExportFiles().filter( + file => file.match(/(validator|member|artifacts|network-info)+/g), + ) return networkFilesList } diff --git a/src/wallet/service/wallet.ts b/src/wallet/service/wallet.ts index f9d84741..aecd3e65 100644 --- a/src/wallet/service/wallet.ts +++ b/src/wallet/service/wallet.ts @@ -1,4 +1,3 @@ - import { ethers } from 'ethers' import { WalletCreateType, WalletType } from '../model/type/wallet.type'