diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c807d..ddc413f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: Build and update image on: push: branches: - - main + - master - generator* paths: - - deployment-generator/** + - container-manager/** tags: - '*' workflow_dispatch: #allow manual trigger to workflow @@ -18,7 +18,7 @@ jobs: environment: DockerHub defaults: run: - working-directory: subnet/deployment-generator + working-directory: container-manager/ steps: - name: Check out code uses: actions/checkout@v3 @@ -41,8 +41,8 @@ jobs: - name: Build and push image run: | - docker build . --file docker/Dockerfile \ - --build-arg SUBNET_BRANCH=${{ steps.commit.outputs.commit}} \ + docker build . --file Dockerfile \ + --build-arg SUBNET_BRANCH=${{ steps.commit.outputs.commit}} \ --build-arg IMAGE_NAME=${{ steps.image.outputs.name }} \ --tag ${{ steps.image.outputs.name }} docker push ${{ steps.image.outputs.name }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8bfc0b0..ce60899 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ keys.json node_modules package-lock.json generated +mount *.env* **temp *key* diff --git a/container-manager/.dockerignore b/container-manager/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/container-manager/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/container-manager/Dockerfile b/container-manager/Dockerfile new file mode 100644 index 0000000..32c8cf9 --- /dev/null +++ b/container-manager/Dockerfile @@ -0,0 +1,21 @@ +# Use the official Node.js 18.15 image as the base +FROM node:18.15 + +# Install Docker CLI (required to interact with the Docker API) +RUN apt-get update && apt-get install -y \ + curl \ + && curl -fsSL https://get.docker.com | sh + +# # Optionally, switch to a non-root user (for testing permissions) +# RUN useradd -m myuser +# USER myuser + +COPY /src /app + +# Set the working directory +WORKDIR /app + +RUN npm install + +# CMD ["sleep", "infinity"] +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/container-manager/src/express.js b/container-manager/src/express.js new file mode 100644 index 0000000..2ef99dc --- /dev/null +++ b/container-manager/src/express.js @@ -0,0 +1,236 @@ +process.chdir(__dirname); +const fs = require("fs"); +const state = require("./libs/state"); +const exec = require("./libs/exec"); +const path = require("path"); +const ethers = require("ethers"); +const consolidate = require("consolidate"); +const express = require("express"); +const app = express(); +const PORT = 5210; +let lastCalled = Date.now(); + +app.engine("pug", consolidate.pug); +app.engine("html", consolidate.swig); +app.set("view engine", "html"); // Set default to HTML +app.set("json spaces", 2); +app.use(express.static(path.join(__dirname, "public"))); +app.use( + express.urlencoded({ + extended: true, + }) +); +app.use(express.json()); + +app.get("/", (req, res) => { + res.sendFile(path.join(__dirname, "views", "index.html")); +}); + +app.get("/debug", (req, res) => { + res.sendFile(path.join(__dirname, "views", "debug.html")); +}); + +app.get("/state", async (req, res) => { + console.log("/state called"); + const thisCall = Date.now(); + console.log("time form last call: ", thisCall - lastCalled); + lastCalled = thisCall; + const response = await state.getState(); + res.setHeader("Content-Type", "application/json"); + res.send(JSON.stringify(response, null, 2)); +}); + +app.get("/deploy_csc_lite", async (req, res) => { + console.log("/deploy_csc_lite called"); + await exec.deployCSC("lite", setupRes(req, res)); +}); +app.get("/deploy_csc_full", async (req, res) => { + console.log("/deploy_csc_full called"); + await exec.deployCSC("full", setupRes(req, res)); +}); +app.get("/deploy_csc_reversefull", async (req, res) => { + console.log("/deploy_csc_reversefull called"); + await exec.deployCSC("reversefull", setupRes(req, res)); +}); + +app.get("/deploy_zero", async (req, res) => { + console.log("/deploy_zero called"); + await exec.deployZero(setupRes(req, res)); +}); + +app.get("/deploy_subswap", async (req, res) => { + console.log("/deploy_subswap called"); + await exec.deploySubswap(setupRes(req, res)); +}); + +app.get("/start_subnet", async (req, res) => { + console.log("/start_subnet called"); + await exec.startComposeProfile("machine1", setupRes(req, res)); +}); + +app.get("/stop_subnet", async (req, res) => { + console.log("/stop_subnet called"); + const callbacks = setupRes(req, res); + await exec.stopComposeProfile("machine1", callbacks); +}); + +app.get("/start_subnet_slow", async (req, res) => { + console.log("/start_subnet_slow called"); + await exec.startSubnet("machine1", setupRes(req, res)); +}); + +app.get("/start_services", async (req, res) => { + console.log("/start_services called"); + await exec.startComposeProfile("services", setupRes(req, res)); +}); + +app.get("/stop_services", async (req, res) => { + console.log("/stop_services called"); + await exec.stopComposeProfile("services", setupRes(req, res)); +}); + +app.get("/start_subswap_frontend", async (req, res) => { + console.log("/start_subswap_frontend called"); + await exec.startComposeProfile("subswap", setupRes(req, res)); +}); + +app.get("/stop_subswap_frontend", async (req, res) => { + console.log("/stop_subswap_frontend called"); + await exec.stopComposeProfile("subswap", setupRes(req, res)); +}); + +app.get("/start_explorer", async (req, res) => { + console.log("/start_explorer called"); + await exec.startComposeProfile("explorer", setupRes(req, res)); +}); + +app.get("/stop_explorer", async (req, res) => { + console.log("/stop_explorer called"); + await exec.stopComposeProfile("explorer", setupRes(req, res)); +}); + +app.get("/remove_subnet", async (req, res) => { + console.log("/remove_subnet called"); + await exec.stopComposeProfile("machine1", setupRes(req, res)); +}); + +// generator methods (also pug instead of html) +app.get("/gen", (req, res) => { + res.render("generator/index.pug", {}); +}); + +app.post("/submit", (req, res) => { + console.log("/submit called"); + const [valid, genOut] = exec.generate(req.body); + + if (!valid) { + res.render("generator/submit.pug", { + message: "failed, please try again", + error: genOut, + }); + } else { + res.render("generator/submit.pug", { + message: + "Config generation success, please continue in the Deployment Wizard tab", + }); + } +}); + +app.post("/submit_preconfig", (req, res) => { + console.log("/submit called"); + const [valid, genOut] = exec.generate(req.body); + + if (!valid) { + res.send("failed to generate"); + } else { + res.send("success"); + } +}); + +app.get("/address", (req, res) => { + const randomWallet = ethers.Wallet.createRandom(); + res.json({ + publicKey: randomWallet.address, + privateKey: randomWallet.privateKey, + }); +}); + +// add method to create env with pk, then faucet wallet will create from that file +app.get("/faucet", (req, res) => { + res.render("faucet/index.pug", {}); +}); + +app.get("/faucet_subnet", async function (req, res) { + console.log("/faucet_subnet called"); + console.log(req.query); + try { + const { subnetUrl, gmKey } = state.getFaucetParams(); + const provider = new ethers.JsonRpcProvider(subnetUrl); + const fromWallet = new ethers.Wallet(gmKey, provider); + const fromPK = "123"; + toAddress = req.query.dest; + amount = req.query.amount; + if (!ethers.isAddress(toAddress)) + throw Error("Invalid destination address"); + if (isNaN(Number(amount)) || parseFloat(amount) <= 0 || amount == "") + throw Error("Invalid Amount"); + if (parseFloat(amount) > 1_000_000_000) + throw Error("Faucet request over 1,000,000,000 is not allowed"); + let inputs = ["", "", fromPK, toAddress, amount]; + const { fromBalance, toBalance, txHash } = await exec.processTransfer( + provider, + fromWallet, + toAddress, + amount + ); + res.json({ + success: true, + sourceAddress: fromWallet.address, + destAddress: toAddress, + sourceBalance: fromBalance, + destBalance: toBalance, + txHash: txHash, + }); + } catch (error) { + console.log(error); + console.log(error.message); + res.json({ + success: false, + message: error.message, + }); + } +}); + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`); +}); + +function sleepSync(ms) { + const start = Date.now(); + while (Date.now() - start < ms) { + // Busy-wait loop (blocks the event loop) + } +} + +function setupRes(req, res) { + res.setHeader("Content-Type", "text/event-stream"); + res.setHeader("Cache-Control", "no-cache"); + res.setHeader("Connection", "keep-alive"); + const dataCallback = (data) => { + lines = data.split("\n"); + for (let l = 0; l < lines.length; l++) { + res.write(`data:${lines[l]}\n\n`); + } + }; + const doneCallback = () => { + res.write("event: close\ndata: Done\n\n"); + res.end(); + }; + req.on("close", () => { + res.end(); + }); + return { + dataCallback: dataCallback, + doneCallback: doneCallback, + }; +} diff --git a/container-manager/src/gen/config_gen.js b/container-manager/src/gen/config_gen.js new file mode 100644 index 0000000..bd5b2f4 --- /dev/null +++ b/container-manager/src/gen/config_gen.js @@ -0,0 +1,226 @@ +const crypto = require("crypto"); +const net = require("net"); +const dotenv = require("dotenv"); +const ethers = require("ethers"); +dotenv.config({ path: `${__dirname}/../../mount/generated/gen.env` }); + +const config = { + deployment_path: process.env.CONFIG_PATH || "", + num_machines: parseInt(process.env.NUM_MACHINE), + num_subnet: parseInt(process.env.NUM_SUBNET), + ip_1: process.env.MAIN_IP || "", + public_ip: process.env.PUBLIC_IP || process.env.MAIN_IP, + network_name: process.env.NETWORK_NAME, + network_id: parseInt( + process.env.NETWORK_ID || Math.floor(Math.random() * (65536 - 1) + 1) + ), + secret_string: + process.env.SERVICES_SECRET || crypto.randomBytes(10).toString("hex"), + relayer_mode: process.env.RELAYER_MODE || "full", + docker_image_name: + process.env.IMAGE_NAME || "xinfinorg/subnet-generator:latest", + version: { + genesis: process.env.VERSION_GENESIS || "v0.3.1", + subnet: process.env.VERSION_SUBNET || "v0.3.2", + bootnode: process.env.VERSION_BOOTNODE || "v0.3.1", + relayer: process.env.VERSION_RELAYER || "v0.3.1", + stats: process.env.VERSION_STATS || "v0.1.11", + frontend: process.env.VERSION_FRONTEND || "v0.1.12", + csc: process.env.VERSION_CSC || "v0.3.0", + zero: process.env.VERSION_ZERO || "v0.3.0", + subswap_frontend: + process.env.VERSION_SUBSWAP_FRONTEND || "v0.1.0", + explorer: process.env.VERSION_EXPLORER || "v0.1.0", + }, + parentnet: { + network: process.env.PARENTNET || "testnet", + url: "", + pubkey: "", + privatekey: process.env.PARENTNET_WALLET_PK || "", + }, + keys: { + subnets_addr: [], + subnets_pk: process.env.SUBNETS_PK || "", + grandmaster_addr: "", + grandmaster_pk: process.env.GRANDMASTER_PK || "", + }, + zero: { + zero_mode: process.env.XDC_ZERO || "", + subnet_wallet_pk: process.env.SUBNET_WALLET_PK || "", + subnet_zero_wallet_pk: process.env.SUBNET_ZERO_WALLET_PK || "", + parentnet_zero_wallet_pk: process.env.PARENTNET_ZERO_WALLET_PK || "", + subswap: process.env.SUBSWAP || "", + }, + generator: { + output_path: `${__dirname}/../../mount/generated/`, + }, +}; + +// if (configSanityCheck(config) === true) { +// module.exports = config; +// } else { +// console.log("bad config init file, please check again"); +// process.exit(); +// } +module.exports = { + config, + configSanityCheck, +}; + +function validatePK(private_key) { + let wallet = new ethers.Wallet(private_key); + return [wallet.address, wallet.privateKey]; +} + +function configSanityCheck(config) { + if (!config.num_machines || !config.num_subnet || !config.network_name) { + console.log("NUM_MACHINE and NUM_SUBNET and NETWORK_NAME must be set"); + process.exit(1); + } + + if (config.num_machines < 1 || config.num_subnet < 1) { + console.log("NUM_MACHINE and NUM_SUBNET must be 1 or more"); + process.exit(1); + } + + if (!net.isIP(config.ip_1)) { + console.log("MAIN_IP Invalid IP address"); + process.exit(1); + } + + if (!net.isIP(config.public_ip)) { + console.log("PUBLIC_IP Invalid IP address"); + process.exit(1); + } + + if (!config.network_name || config.network_name === "") { + console.log("NETWORK_NAME cannot be empty"); + process.exit(1); + } + + if (config.network_id < 1 || config.network_id >= 65536) { + console.log("NETWORK_ID should be in range of 1 to 65536"); + process.exit(1); + } + + if (config.secret_string === "") { + console.log("SERVICES_SECRET cannot be empty string"); + process.exit(1); + } + + if (!(config.relayer_mode === "full" || config.relayer_mode === "lite")) { + console.log("RELAYER_MODE only accepts 'full' or 'lite' (default full)"); + process.exit(1); + } + + if ( + config.parentnet.network === "devnet" || + config.parentnet.network === "testnet" || + config.parentnet.network === "mainnet" + ) { + let official_urls = { + devnet: "https://devnetstats.apothem.network/devnet", + testnet: "https://erpc.apothem.network/", + mainnet: "https://rpc.xinfin.network", + }; + console.log("debug") + console.log(config.parentnet.network) + config.parentnet.url = official_urls[config.parentnet.network]; + console.log(config.parentnet.url) + } else { + console.log("PARENTNET must be devnet or testnet"); + process.exit(1); + } + + if (config.parentnet.privatekey !== "") { + try { + config.parentnet.pubkey = validatePK(config.parentnet.privatekey)[0]; + } catch { + console.log("Invalid PARENTNET_WALLET_PK"); + process.exit(1); + } + } + + if (config.keys.grandmaster_pk !== "") { + try { + [config.keys.grandmaster_addr, config.keys.grandmaster_pk] = validatePK( + config.keys.grandmaster_pk + ); + } catch { + console.log("Invalid GRANDMASTER_PK"); + process.exit(1); + } + } else { + const privatekey = crypto.randomBytes(32).toString("hex"); + [config.keys.grandmaster_addr, config.keys.grandmaster_pk] = + validatePK(privatekey); + } + + if (config.keys.subnets_pk !== "") { + try { + let output_pk = []; + let output_wallet = []; + let pks = config.keys.subnets_pk.split(","); + pks.forEach((pk) => { + const [address, private_key] = validatePK(pk); + output_wallet.push(address); + output_pk.push(private_key); + }); + config.keys.subnets_pk = output_pk; + config.keys.subnets_addr = output_wallet; + } catch { + console.log( + "Invalid SUBNETS_PK please make sure keys are correct length, comma separated with no whitespace or invalid characters" + ); + process.exit(1); + } + + if (config.keys.subnets_addr.length !== config.num_subnet) { + console.log( + `number of keys in SUBNETS_PK (${config.keys.subnets_addr.length}) does not match NUM_SUBNET (${config.num_subnet})` + ); + process.exit(1); + } + + const setPK = new Set(config.keys.subnets_pk); + if (setPK.size !== config.keys.subnets_pk.length) { + console.log("found duplicate keys in SUBNETS_PK"); + process.exit(1); + } + } else { + let output_pk = []; + let output_wallet = []; + for (let i = 0; i < config.num_subnet; i++) { + const privatekey = crypto.randomBytes(32).toString("hex"); + const [address, private_key] = validatePK(privatekey); + output_wallet.push(address); + output_pk.push(private_key); + } + config.keys.subnets_pk = output_pk; + config.keys.subnets_addr = output_wallet; + } + + if (config.zero.zero_mode == "one-directional") { + try { + validatePK(config.zero.parentnet_zero_wallet_pk); + } catch { + console.log("Invalid PARENTNET_ZERO_WALLET_PK"); + process.exit(1); + } + } + + if (config.zero.zero_mode === "bi-directional") { + try { + validatePK(config.zero.subnet_wallet_pk); + validatePK(config.zero.subnet_zero_wallet_pk); + validatePK(config.zero.parentnet_zero_wallet_pk); + } catch { + console.log( + "Invalid SUBNET_WALLET_PK or SUBNET_ZERO_WALLET_PK or PARENTNET_ZERO_WALLET_PK" + ); + process.exit(1); + } + } + + return true; +} diff --git a/container-manager/src/gen/faucet.js b/container-manager/src/gen/faucet.js new file mode 100755 index 0000000..3113b14 --- /dev/null +++ b/container-manager/src/gen/faucet.js @@ -0,0 +1,176 @@ +process.chdir(__dirname); +const fs = require("fs"); +const ethers = require("ethers"); +const path = require("path"); +const { error } = require("console"); +const express = require("express"); +const app = express(); +const router = express.Router(); + +const usage = + "Usage: SUBNET_URL= node faucet.js "; +const subnetURL = process.env.SUBNET_URL || ""; +if (subnetURL == "") { + console.log("Invalid SUBNET_URL"); + console.log(usage); + process.exit(); +} +console.log("RPC url:", subnetURL); + +const inputs = process.argv; +// console.log("inputs", inputs) + +if (inputs.length == 5) { + try { + processTransfer(inputs).then((output) => { + // console.log(output) + process.exit(); + }); + } catch (error) { + console.log(error); + console.log(usage); + process.exit(); + } +} else if (inputs.length == 4) { + faucetServer(inputs); +} else { + console.log("Invalid input length"); + console.log(usage); + process.exit(); +} + +async function processTransfer(inputs) { + fromPK = inputs[2]; + toAddress = inputs[3]; + amount = inputs[4]; + const provider = new ethers.JsonRpcProvider(subnetURL); + const fromWallet = new ethers.Wallet(fromPK, provider); + let tx = { + to: toAddress, + value: ethers.parseEther(amount), + }; + + try { + await provider._detectNetwork(); + } catch (error) { + throw Error("Cannot connect to RPC"); + } + + let sendPromise = fromWallet.sendTransaction(tx); + txHash = await sendPromise.then((tx) => { + return tx.hash; + }); + console.log("TX submitted, confirming TX execution, txhash:", txHash); + + let receipt; + let count = 0; + while (!receipt) { + count++; + // console.log("tx receipt check loop", count); + if (count > 60) { + throw Error("Timeout: transaction did not execute after 60 seconds"); + } + await sleep(1000); + let receipt = await provider.getTransactionReceipt(txHash); + if (receipt && receipt.status == 1) { + console.log("Successfully transferred", amount, "subnet token"); + let fromBalance = await provider.getBalance(fromWallet.address); + fromBalance = ethers.formatEther(fromBalance); + let toBalance = await provider.getBalance(toAddress); + toBalance = ethers.formatEther(toBalance); + console.log("Current balance"); + console.log(`${fromWallet.address}: ${fromBalance}`); + console.log(`${toAddress}: ${toBalance}`); + return { + fromBalance: fromBalance, + toBalance: toBalance, + txHash: txHash, + }; + } + } +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function faucetServer(inputs) { + try { + command = inputs[2]; + fromPK = inputs[3]; + fromWallet = new ethers.Wallet(fromPK); + + if (command != "server") { + throw Error("Invalid command"); + } + } catch (error) { + console.log(error); + console.log( + "Usage: SUBNET_URL= node faucet.js server " + ); + process.exit(); + } + app.set("view engine", "pug"); + app.set("views", path.join(__dirname, "views_faucet")); + router.use( + express.urlencoded({ + extended: true, + }) + ); + app.use(express.static("css")); + + router.get("/", (req, res) => { + res.render("index", {}); + }); + + router.get("/address", (req, res) => { + const randomWallet = ethers.Wallet.createRandom(); + res.json({ + publicKey: randomWallet.address, + privateKey: randomWallet.privateKey, + }); + }); + + router.get("/source", (req, res) => { + res.json({ + source: fromWallet.address, + }); + }); + + router.get("/faucet", async function (req, res) { + console.log(req.query); + try { + toAddress = req.query.dest; + amount = req.query.amount; + if (!ethers.isAddress(toAddress)) + throw Error("Invalid destination address"); + if (isNaN(Number(amount)) || parseFloat(amount) <= 0 || amount == "") + throw Error("Invalid Amount"); + if (parseFloat(amount) > 1000000) + throw Error("Faucet request over 1,000,000 is not allowed"); + let inputs = ["", "", fromPK, toAddress, amount]; + const { fromBalance, toBalance, txHash } = await processTransfer(inputs); + res.json({ + success: true, + sourceAddress: fromWallet.address, + destAddress: toAddress, + sourceBalance: fromBalance, + destBalance: toBalance, + txHash: txHash, + }); + } catch (error) { + console.log(error); + console.log(error.message); + res.json({ + success: false, + message: error.message, + }); + } + }); + + app.use("/", router); + + app.listen(process.env.port || 5211); + console.log("Running at Port 5211"); + console.log("visit: http://127.0.0.1:5211"); +} diff --git a/container-manager/src/gen/gen.js b/container-manager/src/gen/gen.js new file mode 100644 index 0000000..587f587 --- /dev/null +++ b/container-manager/src/gen/gen.js @@ -0,0 +1,199 @@ +const fs = require("fs"); +const yaml = require("js-yaml"); +const { exit } = require("process"); +const configModule = require("./config_gen"); +const gen_compose = require("./gen_compose"); +const gen_env = require("./gen_env"); +const gen_other = require("./gen_other"); + +const config = configModule.config; +configModule.configSanityCheck(config); +Object.freeze(config); +console.log("config", config); + +// const num_machines = config.num_machines +// const num_subnet = config.num_subnet +// const ip_1 = config.ip_1 +// const network_name = config.network_name +// const network_id = config.network_id +// const secret_string = config.secret_string +// const output_path = `${__dirname}/../generated/` + +const keys = gen_other.genSubnetKeys(); + +const num_per_machine = Array(config.num_machines); +// integer division +for (let i = 0; i < config.num_machines; i++) { + num_per_machine[i] = Math.floor(config.num_subnet / config.num_machines); +} +// divide up the remainder +for (let i = 0; i < config.num_subnet % config.num_machines; i++) { + num_per_machine[i]++; +} +num_per_machine.reverse(); // let first machines host services, put fewer subnets + +// gen docker-compose +let doc = { + // version: "3.7", //docker deprecated attribute + services: {}, +}; + +let start_num = 1; +for (let i = 1; i <= config.num_machines; i++) { + const subnet_nodes = gen_compose.genSubnetNodes( + (machine_id = i), + (num = num_per_machine[i - 1]), + (start_num = start_num) + ); + start_num += num_per_machine[i - 1]; + Object.entries(subnet_nodes).forEach((entry) => { + const [key, value] = entry; + doc["services"][key] = value; + }); +} + +// gen subnets configs +const subnet_services = gen_compose.genServices((machine_id = 1)); +Object.entries(subnet_services).forEach((entry) => { + const [key, value] = entry; + doc["services"][key] = value; +}); + +const subswap_compose = gen_compose.genSubswapFrontend(); +Object.entries(subswap_compose).forEach((entry) => { + const [key, value] = entry; + doc["services"][key] = value; +}); + +const explorer_compose = gen_compose.genExplorer(); +Object.entries(explorer_compose).forEach((entry) => { + const [key, value] = entry; + doc["services"][key] = value; +}); + +// checkpoint smartcontract deployment config +doc, (ip_record = gen_compose.injectNetworkConfig(doc)); +const commonconf = gen_env.genServicesConfig(ip_record); +subnetconf = []; +for (let i = 1; i <= config.num_subnet; i++) { + subnetconf.push(gen_env.genSubnetConfig(i, keys, ip_record)); +} +const deployconf = gen_env.genContractDeployEnv(ip_record); + +const compose_content = yaml.dump(doc, {}); + +const genesis_input = gen_other.genGenesisInputFile( + config.network_name, + config.network_id, + config.num_subnet, + keys +); +const genesis_input_file = yaml.dump(genesis_input, {}); + +writeGenerated(config.generator.output_path); +copyScripts(config.generator.output_path); + +console.log("gen successful"); + +function writeGenerated(output_dir) { + // const pathCommand = + // `cd ${mountPath};\n` + + // `export PWD=${config.hostPath};\n` + + // `${command} + + // writing files + // fs.rmSync(`${output_path}`, { recursive: true, force: true }); //wont work with docker mount + // fs.mkdirSync(`${output_path}`) //won't work with docker mount + fs.writeFileSync(`${output_dir}/placeholder.txt`, "-", (err) => { + if (err) { + console.error(err); + exit(); + } + }); + + fs.writeFileSync( + `${output_dir}/docker-compose.yml`, + compose_content, + (err) => { + if (err) { + console.error(err); + exit(); + } + } + ); + + fs.writeFileSync(`${output_dir}/common.env`, commonconf, (err) => { + if (err) { + console.error(err); + exit(); + } + }); + + fs.writeFileSync(`${output_dir}/contract_deploy.env`, deployconf, (err) => { + if (err) { + console.error(err); + exit(); + } + }); + + const keys_json = JSON.stringify(keys, null, 2); + fs.writeFile(`${output_dir}/keys.json`, keys_json, (err) => { + if (err) { + console.error("Error writing key file:", err); + exit(); + } + }); + + for (let i = 1; i <= config.num_subnet; i++) { + fs.writeFileSync( + `${output_dir}/subnet${i}.env`, + subnetconf[i - 1], + (err) => { + if (err) { + console.error(err); + exit(); + } + } + ); + } + + fs.writeFileSync( + `${output_dir}/genesis_input.yml`, + genesis_input_file, + (err) => { + if (err) { + console.error(err); + exit(); + } + } + ); +} + +function copyScripts(output_dir) { + fs.writeFileSync(`${output_dir}/scripts/placeholder.txt`, "-", (err) => { + if (err) { + console.error(err); + exit(); + } + }); + fs.copyFileSync( + `${__dirname}/scripts/check-mining.sh`, + `${output_dir}/scripts/check-mining.sh` + ); + fs.copyFileSync( + `${__dirname}/scripts/check-peer.sh`, + `${output_dir}/scripts/check-peer.sh` + ); + fs.copyFileSync( + `${__dirname}/scripts/faucet.sh`, + `${output_dir}/scripts/faucet.sh` + ); + fs.copyFileSync( + `${__dirname}/scripts/faucet-server.sh`, + `${output_dir}/scripts/faucet-server.sh` + ); + fs.copyFileSync( + `${__dirname}/scripts/add-node.sh`, + `${output_dir}/scripts/add-node.sh` + ); +} diff --git a/container-manager/src/gen/gen_compose.js b/container-manager/src/gen/gen_compose.js new file mode 100644 index 0000000..2e66da3 --- /dev/null +++ b/container-manager/src/gen/gen_compose.js @@ -0,0 +1,184 @@ +const net = require("net"); +const configModule = require("./config_gen"); +const config = configModule.config; +Object.freeze(config); + +module.exports = { + genSubnetNodes, + genBootNode, + genServices, + injectNetworkConfig, + genSubswapFrontend, + genExplorer, +}; + +function genSubnetNodes(machine_id, num, start_num = 1) { + let subnet_nodes = {}; + for (let i = start_num; i < start_num + num; i++) { + const node_name = "subnet" + i.toString(); + const volume = "${HOSTPWD}/xdcchain" + i.toString() + ":/work/xdcchain"; + const config_path = "subnet" + i.toString() + ".env"; + const compose_profile = "machine" + machine_id.toString(); + const port = 20302 + i; + const rpcport = 8544 + i; + const wsport = 9554 + i; + subnet_nodes[node_name] = { + image: `xinfinorg/xdcsubnets:${config.version.subnet}`, + volumes: [volume, "${HOSTPWD}/genesis.json:/work/genesis.json"], + restart: "always", + network_mode: "host", + env_file: [config_path], + profiles: [compose_profile], + ports: [ + `${port}:${port}/tcp`, + `${port}:${port}/udp`, + `${rpcport}:${rpcport}/tcp`, + `${rpcport}:${rpcport}/udp`, + `${wsport}:${wsport}/tcp`, + `${wsport}:${wsport}/udp`, + ], + }; + } + return subnet_nodes; +} + +function genBootNode(machine_id) { + let config_path = "common.env"; + const machine = "machine" + machine_id.toString(); + const bootnode = { + image: `xinfinorg/xdcsubnets:${config.version.bootnode}`, + restart: "always", + env_file: config_path, + volumes: ["${HOSTPWD}/bootnodes:/work/bootnodes"], + entrypoint: ["bash", "/work/start-bootnode.sh"], + command: ["-verbosity", "6", "-nodekey", "bootnode.key"], + ports: ["20301:20301/tcp", "20301:20301/udp"], + profiles: [machine], + }; + return bootnode; +} + +function genServices(machine_id) { + const config_path = "common.env"; + const machine = "services"; + const volume_path = "${HOSTPWD}" + "/" + config_path; + const frontend = { + image: `xinfinorg/subnet-frontend:${config.version.frontend}`, + restart: "always", + env_file: config_path, // not used directly (injected via volume) but required to trigger restart if common.env changes + volumes: [`${volume_path}:/app/.env.local`], + ports: ["5214:5214"], + profiles: [machine], + }; + const relayer = { + image: `xinfinorg/xdc-relayer:${config.version.relayer}`, + restart: "always", + env_file: config_path, + ports: ["5215:5215"], + profiles: [machine], + }; + const stats = { + image: `xinfinorg/subnet-stats-service:${config.version.stats}`, + restart: "always", + env_file: config_path, + volumes: ["${HOSTPWD}/stats-service/logs:/app/logs"], + ports: ["5213:5213"], + profiles: [machine], + }; + const bootnode = genBootNode(machine_id); + // observer=genObserver(machine_id), + + const services = { + bootnode, + // observer, + relayer, + stats, + frontend, + }; + + return services; +} + +function genSubswapFrontend() { + const subswap_frontend = { + image: `xinfinorg/subswap-frontend:${config.version.subswap_frontend}`, + restart: "always", + volumes: [ + "${HOSTPWD}/subswap-frontend.config.json:/app/subswap-frontend.config.json", + ], + ports: ["5216:5216"], + profiles: ["subswap"], + }; + const subswap = { + subswap_frontend, + }; + return subswap; +} + +function genExplorer() { + const explorer_db = {}; + const explorer_ui = {}; + const explorer_indexer = {}; + + const explorer = { + // db: explorer_db, + // ui: explorer_ui, + // idx: explorer_indexer, + }; + return explorer; +} + +function injectNetworkConfig(compose_object) { + // networks: + // docker_net: + // driver: bridge + // ipam: + // config: + // - subnet: 192.168.25.0/24 + const network = { + docker_net: { + external: true, + driver: "bridge", + ipam: { + config: [{ subnet: "192.168.25.0/24" }], + }, + }, + }; + // networks: + // docker_net: + // ipv4_address: + // 192.168.25.10 + + let record_services_ip = {}; + + const ip_string_base = "192.168.25."; + let start_ip_subnet = 11; + let start_ip_service = 51; + Object.entries(compose_object["services"]).forEach((entry) => { + const [key, value] = entry; + let component_ip; + if (key.startsWith("subnet")) { + component_ip = ip_string_base + parseInt(start_ip_subnet); + start_ip_subnet += 1; + } else { + component_ip = ip_string_base + parseInt(start_ip_service); + start_ip_service += 1; + } + if (!net.isIP(component_ip)) { + console.log(`ERROR: found invalid IP assignment ${component_ip}`); + process.exit(1); + } + const component_network = { + docker_net: { + ipv4_address: component_ip, + }, + }; + compose_object["services"][key]["networks"] = component_network; + delete compose_object["services"][key]["network_mode"]; + record_services_ip[key] = component_ip; + }); + + compose_object["networks"] = network; + + return compose_object, record_services_ip; +} diff --git a/container-manager/src/gen/gen_env.js b/container-manager/src/gen/gen_env.js new file mode 100644 index 0000000..2dd9c52 --- /dev/null +++ b/container-manager/src/gen/gen_env.js @@ -0,0 +1,103 @@ +const configModule = require("./config_gen"); +const config = configModule.config; +Object.freeze(config); + +module.exports = { + genSubnetConfig, + genServicesConfig, + genContractDeployEnv, +}; + +function genSubnetConfig(subnet_id, key, ip_record) { + const key_name = `key${subnet_id}`; + let private_key = key[key_name]["PrivateKey"]; + private_key = private_key.slice(2, private_key.length); // remove 0x for subnet conf + const port = 20303 + subnet_id - 1; + const rpcport = 8545 + subnet_id - 1; + const wsport = 9555 + subnet_id - 1; + const bootnode_ip = + config.num_machines === 1 ? ip_record["bootnode"] : config.ip_1; + const stats_ip = config.num_machines === 1 ? ip_record["stats"] : config.ip_1; + const config_env = ` +INSTANCE_NAME=subnet${subnet_id} +PRIVATE_KEY=${private_key} +BOOTNODES=enode://cc566d1033f21c7eb0eb9f403bb651f3949b5f63b40683917\ +765c343f9c0c596e9cd021e2e8416908cbc3ab7d6f6671a83c85f7b121c1872f8be\ +50a591723a5d@${bootnode_ip}:20301 +NETWORK_ID=${config.network_id} +SYNC_MODE=full +RPC_API=db,eth,debug,miner,net,shh,txpool,personal,web3,XDPoS +STATS_SERVICE_ADDRESS=${stats_ip}:5213 +STATS_SECRET=${config.secret_string} +PORT=${port} +RPCPORT=${rpcport} +WSPORT=${wsport} +LOG_LEVEL=2 +`; + + return config_env; +} + +function genServicesConfig() { + const url = config.parentnet.url; + const bootnode_ip = + config.num_machines === 1 ? ip_record["bootnode"] : config.ip_1; + const subnet_ip = + config.num_machines === 1 ? ip_record["subnet1"] : config.ip_1; + let config_env = ` +# Bootnode +EXTIP=${bootnode_ip} +BOOTNODE_PORT=20301 + +# Stats and relayer +PARENTNET_URL=${url} +PARENTNET_WALLET=${config.parentnet.pubkey} +SUBNET_URL=http://${subnet_ip}:8545 +RELAYER_MODE=${config.relayer_mode} +SLACK_WEBHOOK=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX +CORS_ALLOW_ORIGIN=* + +# Frontend +VITE_SUBNET_URL=http://${config.public_ip}:5213 +VITE_SUBNET_RPC=http://${config.public_ip}:8545 + +# Share Variable +STATS_SECRET=${config.secret_string} + +# CSC +PARENTNET_WALLET_PK=${config.parentnet.privatekey} + +`; + + if (config.zero.zero_mode == "one-directional") { + config_env += ` +# XDC-ZERO +PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} + `; + } else if (config.zero.zero_mode == "bi-directional") { + config_env += ` +# XDC-ZERO +PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} +SUBNET_WALLET_PK=${config.zero.subnet_wallet_pk} +SUBNET_ZERO_WALLET_PK=${config.zero.subnet_zero_wallet_pk} + `; + } + // # Parent Chain Observe Node + // PARENTNET_NODE_NAME=mainnet_observer + // PRIVATE_KEYS=11111111111111111111111111111111111111111111111111111111111111 + return config_env; +} + +function genContractDeployEnv(ip_record) { + const subnet_ip = + config.num_machines === 1 ? ip_record["subnet1"] : config.ip_1; + const config_deploy = ` +PARENTNET_URL=${config.parentnet.url} +SUBNET_URL=http://${subnet_ip}:8545 + +PARENTNET_PK=${config.parentnet.privatekey} +SUBNET_PK=${config.keys.grandmaster_pk} + +`; + return config_deploy; +} diff --git a/container-manager/src/gen/gen_other.js b/container-manager/src/gen/gen_other.js new file mode 100644 index 0000000..3062844 --- /dev/null +++ b/container-manager/src/gen/gen_other.js @@ -0,0 +1,265 @@ +const configModule = require("./config_gen"); +const config = configModule.config; +Object.freeze(config); + +module.exports = { + genSubnetKeys, + genDeploymentJson, + genCommands, + genGenesisInputFile, +}; + +function genSubnetKeys() { + const keys = {}; + // const num = config.keys.subnets_addr.length + for (let i = 0; i < config.keys.subnets_addr.length; i++) { + const key = `key${i + 1}`; + const private_key = config.keys.subnets_pk[i]; + const address = config.keys.subnets_addr[i]; + keys[key] = { + PrivateKey: private_key, + "0x": address, + short: address.replace(/^0x/i, ""), + }; + } + keys["Grandmaster"] = { + PrivateKey: config.keys.grandmaster_pk, + "0x": config.keys.grandmaster_addr, + short: config.keys.grandmaster_addr.replace(/^0x/i, ""), + }; + if (Object.keys(keys).length !== config.num_subnet + 1) { + // sanity check + console.log("bad case, key length not equal number of subnets"); + console.log(Object.keys(keys).length, config.num_subnet + 1); + console.log(keys); + process.exit(); + } + + return keys; +} + +function genDeploymentJson(keys) { + const num = Object.keys(keys).length - 1; + const validators = []; + for (let i = 1; i <= num; i++) { + let key_name = `key${i}`; + let public_key = keys[key_name]["0x"]; + validators.push(public_key); + } + + const deployment = { + validators, + gap: 450, + epoch: 900, + }; + return deployment; +} + +// function genNetworkJson(){ //don't need this, config will overlap(duplicate) with other files, shell script in CSC docker can gen this +// let network = { +// "xdcsubnet": `http://${config.ip_1}:8545`, +// "xdcparentnet": config.parentnet.url +// } +// return network +// } +// function genNetworkJsonMac(){ +// let network = { +// "xdcsubnet": `http://127.0.0.1:8545`, +// "xdcparentnet": config.parentnet.url +// } +// return network +// } + +function genCommands() { + const csc_mode = config.relayer_mode === "lite" ? "lite" : "full"; + let commands = ""; + commands += "Start under generated/ directory\n"; + commands += "\n1. Deploy Subnet nodes\n"; + for (let i = 1; i <= config.num_machines; i++) { + const machine_name = "machine" + i.toString(); + if (config.num_machines > 1) { + commands += + machine_name + `: deploy subnet on machine${i}\n`; + } + if (i !== 1) { + commands += ` Prerequisite: copy docker-compose.yml, genesis.json, config/subnetX.env to ${machine_name}.\n`; + } + commands += ` docker compose --profile ${machine_name} pull\n`; + commands += ` docker compose --profile ${machine_name} up -d\n\n`; + } + commands += + "\n2. After 60 seconds, confirm the Subnet is running correctly\n"; + commands += " ./scripts/check-mining.sh\n"; + commands += "\n3. Deploy Checkpoint Smart Contract (CSC)\n"; + commands += ` docker pull xinfinorg/csc:${config.version.csc}\n`; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/csc:${config.version.csc} ${csc_mode}\n`; + commands += "\n4. Start services (relayer, backend, frontend)\n"; + commands += ` docker compose --profile services pull\n`; + commands += ` docker compose --profile services up -d\n`; + commands += "\n5. Confirm Subnet services through browser UI\n"; + commands += ` Frontend: http://${config.public_ip}:5214\n`; + commands += ` Relayer: http://${config.public_ip}:5215\n`; + + if (config.zero.zero_mode == "") { + } else if (config.zero.zero_mode == "one-directional") { + if (config.zero.subswap == "true") { + commands += "\n6. Deploy XDC-Zero and Subswap\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} zeroandsubswap\n`; + commands += "\n7. Run Subswap Frontend\n"; + commands += ` docker compose --profile subswap pull\n`; + commands += ` docker compose --profile subswap up -d\n`; + commands += "\n8. Confirm Subswap UI\n"; + commands += ` Subswap-Frontend: http://${config.public_ip}:5216\n`; + } else { + commands += "\n6. Deploy XDC-Zero\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; + } + commands += "\nRestart Relayer\n"; + commands += ` docker compose --profile services down\n`; + commands += ` docker compose --profile services up -d\n`; + } else if (config.zero.zero_mode == "bi-directional") { + commands += "\n6. Deploy Reverse CSC\n"; + commands += ` docker pull xinfinorg/csc:${config.version.csc}\n`; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/csc:${config.version.csc} reversefull\n`; + if (config.zero.subswap == "true") { + commands += "\n7. Deploy XDC-Zero and Subswap\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} zeroandsubswap\n`; + commands += "\n8. Run Subswap Frontend\n"; + commands += ` docker compose --profile subswap_frontend pull\n`; + commands += ` docker compose --profile subswap_frontend up -d\n`; + commands += "\n9. Confirm Subswap UI\n"; + commands += ` Subswap-Frontend: http://${config.public_ip}:5216\n`; + } else { + commands += "\n7. Deploy XDC-Zero\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; + } + commands += "\nRestart Relayer\n"; + commands += ` docker compose --profile services down\n`; + commands += ` docker compose --profile services up -d\n`; + } else { + console.log("Error: Invalid XDC-Zero mode"); + exit(); + } + + // if (config.zero.zero_mode == "") { + // } else if (config.zero.zero_mode == "one-directional") { + // commands += "\n\nDeploy XDC-Zero crosschain framework (one-directional)\n"; + // commands += "\n1. Add CSC configuration to contract_deploy.env\n"; + // commands += + // " - copy CHECKPOINT_CONTRACT from common.env to and rename it to CSC in contract_deploy.env\n"; + // commands += + // " - copy CHECKPOINT_CONTRACT from common.env to and rename it to REVERSE_CSC in contract_deploy.env\n"; + // commands += "\n2. Deploy XDC-Zero\n"; + // commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + // commands += ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; + // if (config.zero.subswap == "true") { + // commands += " \n Deploy Subswap and Register Application to XDC-Zero\n"; + // commands += + // " - copy SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 subswap\n`; + // commands += + // " - copy SUBNET_APP and PARENTNET_APP to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 applicationregister\n`; + // } + // commands += "\n3. Add XDC-Zero Configuration to common.env\n"; + // commands += + // " - copy step 2. output SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to common.env\n"; + // commands += + // " - Add PARENTNET_ZERO_WALLET_PK to common.env, this should be different from PARENTNET_WALLET_PK\n"; + // commands += "\n4. Restart Relayer\n"; + // commands += ` docker compose --profile services down\n`; + // commands += ` docker compose --profile services up -d\n`; + // commands += "\n5. Confirm Relayer is running \n"; + // commands += ` Relayer: http://${config.public_ip}:5215\n`; + // commands += ` Frontend: http://${config.public_ip}:5214\n`; + + // } else if (config.zero.zero_mode == "bi-directional") { + // commands += "\n\nDeploy XDC-Zero crosschain framework (bi-directional)\n"; + // commands += "\n1. Deploy Reverse Checkpoint Smart Contract (Reverse CSC)\n"; + // commands += ` docker pull xinfinorg/csc:${config.version.csc}\n`; + // commands += ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/csc:${config.version.csc} reversefull\n`; + // commands += "\n2. Add CSC and Reverse CSC configuration\n"; + // commands += + // " - copy previous step output REVERSE_CHECKPOINT_CONTRACT to common.env\n"; + // commands += + // " - copy REVERSE_CHECKPOINT_CONTRACT from common.env and rename it to REVERSE_CSC in contract_deploy.env\n"; + // commands += + // " - copy CHECKPOINT_CONTRACT from common.env and rename it to CSC in contract_deploy.env\n"; + // commands += "\n3. Deploy XDC-Zero\n"; + // commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + // commands += ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; + // if (config.zero.subswap) { + // commands += + // " \n Deploy Subswap and Register Application to XDC-Zero\n"; + // commands += + // " - copy SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 subswap\n`; + // commands += + // " - copy SUBNET_APP and PARENTNET_APP to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 applicationregister\n`; + // } + // commands += "\n4. Add XDC-Zero Configuration to common.env\n"; + // commands += + // " - copy step 3. output SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to common.env\n"; + // commands += + // " - Add SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK to common.env, they should be different from each other\n"; + // commands += + // " - Add PARENTNET_ZERO_WALLET_PK to common.env, this should be different from PARENTNET_WALLET_PK\n"; + // commands += "\n5. Transfer Subnet tokens to SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK\n"; + // commands += " ./scripts/faucet.sh\n" + // commands += " - check keys.json for the Grandmaster Key as the source wallet\n" + // commands += " - for the destination wallet you should use the public address of SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK\n" + // commands += "\n6. Restart Relayer\n"; + // commands += ` docker compose --profile services down\n`; + // commands += ` docker compose --profile services up -d\n`; + // commands += "\n7. Confirm Relayer is running \n"; + // commands += ` Relayer: http://${config.public_ip}:5215\n`; + // commands += ` Frontend: http://${config.public_ip}:5214\n`; + // } else { + // console.log("Error: Invalid XDC-Zero mode"); + // exit(); + // } + + return commands; +} + +function genGenesisInputFile(network_name, network_id, num_subnet, keys) { + const masternodes = []; + const grandmasters = []; + Object.keys(keys).forEach(function (k) { + const v = keys[k]; + if (k === "Grandmaster") { + grandmasters.push(v["0x"]); + } else { + masternodes.push(v["0x"]); + } + }); + + const config = { + name: network_name, + grandmasters, + masternodes, + chainid: network_id, + }; + return config; +} diff --git a/container-manager/src/gen/package.json b/container-manager/src/gen/package.json new file mode 100644 index 0000000..c293b07 --- /dev/null +++ b/container-manager/src/gen/package.json @@ -0,0 +1,18 @@ +{ + "dependencies": { + "dotenv": "^16.3.1", + "ethers": "^6.3.0", + "express": "^4.19.2", + "fs": "^0.0.1-security", + "js-yaml": "^4.1.0", + "pug": "^3.0.3", + "readline-sync": "^1.4.10" + }, + "devDependencies": { + "eslint": "^8.56.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.1", + "eslint-plugin-promise": "^6.1.1" + } +} diff --git a/container-manager/src/gen/scripts/add-node.sh b/container-manager/src/gen/scripts/add-node.sh new file mode 100755 index 0000000..a79fe23 --- /dev/null +++ b/container-manager/src/gen/scripts/add-node.sh @@ -0,0 +1,195 @@ +#!/bin/bash + +set -eo pipefail +cd "$(dirname "$0")" +FILE="../docker-compose.yml" + +# Detect OS type +OS_TYPE=$(uname) + +# Function to handle grep on macOS and Linux differently +function grep_command() { + if [[ "$OS_TYPE" == "Darwin" ]]; then + grep -oE "$1" "$2" + else + grep -oP "$1" "$2" + fi +} + +# Function to handle sed on macOS and Linux differently +function sed_command() { + if [[ "$OS_TYPE" == "Darwin" ]]; then + sed -i '' "$1" "$2" + else + sed -i "$1" "$2" + fi +} + +# Read the current maximum subnet ID (for incrementing) +MAX_SUBNET_ID=$(grep_command 'subnet[0-9]+' "$FILE" | sed 's/subnet//' | sort -n | tail -n 1) + +if [ -z "$MAX_SUBNET_ID" ]; then + NEW_SUBNET_ID=1 +else + NEW_SUBNET_ID=$((MAX_SUBNET_ID + 1)) +fi + +# Calculate the port numbers and IPv4 address +PORT=$((20302 + NEW_SUBNET_ID)) +RPCPORT=$((8544 + NEW_SUBNET_ID)) +WSPORT=$((9554 + NEW_SUBNET_ID)) +IPV4_ADDRESS=$((10+4 + NEW_SUBNET_ID)) + +# Function to add new subnet configuration to docker-compose.yml +function add_docker_compose_linux() { + local service_indent="" + local found_subnet=0 + local subnet_template="" + + # Pick the first subnet entry + while IFS= read -r line; do + # Check for services start + if [[ $line =~ ^[[:space:]]*services: ]]; then + service_indent=$(echo "$line" | grep -o '^[[:space:]]*') + fi + + # Check for the first subnet entry + if [[ $line =~ ^[[:space:]]*subnet[0-9]+: ]]; then + found_subnet=1 + SUBNET_INDENT=$(echo "$line" | grep -o '^[[:space:]]*') + fi + + # If in a subnet, save the content as a template + if [[ $found_subnet -eq 1 ]]; then + subnet_template+="$line\n" + if [[ $line =~ ^${SUBNET_INDENT}.*ipv4_address: ]]; then + found_subnet=0 + break + fi + fi + done < "$FILE" + + # Exit if no subnet is found + if [[ -z $subnet_template ]]; then + echo "Error: No subnet entries found in $FILE" + exit 1 + fi + + # -e "s/subnet[0-9]+/subnet$NEW_SUBNET_ID/" \ + # Modify the template to generate a new entry + NEW_SUBNET=$(echo -e "$subnet_template" | sed \ + -e "s/subnet[0-9]\+/subnet$NEW_SUBNET_ID/" \ + -e "s/xdcchain[0-9]\+/xdcchain$NEW_SUBNET_ID/" \ + -e "s/2030[0-9]\+:2030[0-9]\+/$PORT:$PORT/" \ + -e "s/854[0-9]\+:854[0-9]\+/$RPCPORT:$RPCPORT/" \ + -e "s/955[0-9]\+:955[0-9]\+/$WSPORT:$WSPORT/" \ + -e "s/192\.168\.25\.[0-9]\+/192.168.25.$IPV4_ADDRESS/") + + # Use sed to insert the new subnet configuration after "services:" + if grep -q "^services:" "$FILE"; then + # Escape backslashes and newlines for macOS compatibility + ESCAPED_SUBNET=$(echo "$NEW_SUBNET" | sed 's/[\/&]/\\&/g' | sed ':a;N;$!ba;s/\n/\\n/g') + sed_command="/^services:/a \\ +$ESCAPED_SUBNET" + sed -i.bak -e "$sed_command" "$FILE" + echo "Added new subnet${NEW_SUBNET_ID} configuration to docker-compose.yml!" + else + echo "Error: 'services:' not found in $FILE!" + exit 1 + fi +} + +function add_docker_compose_mac() { + local service_indent="" + local found_subnet=0 + local subnet_template="" + + # Pick the first subnet entry + while IFS= read -r line; do + # Check for services start + if [[ $line =~ ^[[:space:]]*services: ]]; then + service_indent=$(echo "$line" | grep -o '^[[:space:]]*') + fi + + # Check for the first subnet entry + if [[ $line =~ ^[[:space:]]*subnet[0-9]+: ]]; then + found_subnet=1 + SUBNET_INDENT=$(echo "$line" | grep -o '^[[:space:]]*') + fi + + # If in a subnet, save the content as a template + if [[ $found_subnet -eq 1 ]]; then + subnet_template+="$line\n" + if [[ $line =~ ^${SUBNET_INDENT}.*ipv4_address: ]]; then + found_subnet=0 + break + fi + fi + done < "$FILE" + + # Exit if no subnet is found + if [[ -z $subnet_template ]]; then + echo "Error: No subnet entries found in $FILE" + exit 1 + fi + + # Modify the template to generate a new entry + NEW_SUBNET=$(echo -e "$subnet_template" | sed -E \ + -e "s/subnet[0-9]+/subnet$NEW_SUBNET_ID/" \ + -e "s/xdcchain[0-9]+/xdcchain$NEW_SUBNET_ID/" \ + -e "s/2030[0-9]+:2030[0-9]+/$PORT:$PORT/" \ + -e "s/854[0-9]+:854[0-9]+/$RPCPORT:$RPCPORT/" \ + -e "s/955[0-9]+:955[0-9]+/$WSPORT:$WSPORT/" \ + -e "s/192.168.25.[0-9]+/192.168.25.$IPV4_ADDRESS/") + + # Use sed to insert the new subnet configuration after "services:" + if grep -q "^services:" "$FILE"; then + # Escape backslashes and newlines for macOS compatibility + ESCAPED_SUBNET=$(echo "$NEW_SUBNET" | sed 's/[\/&]/\\&/g') + ESCAPED_SUBNET="${ESCAPED_SUBNET//$'\n'/*}*" + sed_command="/^services:/a\\ +$ESCAPED_SUBNET" + cp ../docker-compose.yml ../docker-compose.yml.bak + sed -i '' "$sed_command" "$FILE" + sed -i '' "s/*/\n/g" "$FILE" + echo "Added new subnet${NEW_SUBNET_ID} configuration to docker-compose.yml!" + else + echo "Error: 'services:' not found in $FILE!" + exit 1 + fi +} + +# Function to create a new .env file and modify its content +function create_new_env_file() { + read -p "Input private_key: " private_key + local source_file="../subnet1.env" + local new_file="../subnet${NEW_SUBNET_ID}.env" + + # Check if the source file exists + if [ ! -f "$source_file" ]; then + echo "Source file $source_file does not exist!" + exit 1 + fi + + # Read the source file content and modify the specified entry + cp "$source_file" "$new_file" + + # Modify the specified entries + sed_command "s/^INSTANCE_NAME=.*$/INSTANCE_NAME=${NEW_SUBNET_ID}/" "$new_file" + sed_command "s/^PRIVATE_KEY=.*$/PRIVATE_KEY=${private_key}/" "$new_file" + sed_command "s/^PORT=.*$/PORT=${PORT}/" "$new_file" + sed_command "s/^RPCPORT=.*$/RPCPORT=${RPCPORT}/" "$new_file" + sed_command "s/^WSPORT=.*$/WSPORT=${WSPORT}/" "$new_file" + sed_command "s/^LOG_LEVEL=.*$/LOG_LEVEL=2/" "$new_file" + + echo "New subnet${NEW_SUBNET_ID}.env file created!" +} + +# Execute functions +if [[ "$OS_TYPE" == "Darwin" ]]; then + add_docker_compose_mac +else + add_docker_compose_linux +fi + +create_new_env_file diff --git a/container-manager/src/gen/scripts/check-mining.sh b/container-manager/src/gen/scripts/check-mining.sh new file mode 100755 index 0000000..fd7345f --- /dev/null +++ b/container-manager/src/gen/scripts/check-mining.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "getting latest block" +resp=$(curl -s --location 'http://localhost:8545' \ +--header 'Content-Type: application/json' \ +--data '{"jsonrpc":"2.0","method":"XDPoS_getV2BlockByNumber","params":["latest"],"id":1}') + +num=$(echo $resp | grep -o '"Number":[0-9]*' | cut -d':' -f2 | tr -d ' ') +echo $num + +if [[ $num == "null" ]] || [[ $num == "" ]]; then + echo "no block has been mined, please check if nodes are peering properly" + exit +else + for i in 2 3 4 + do + sleep 3 + echo "getting latest block $i" + resp=$(curl -s --location 'http://localhost:8545' \ + --header 'Content-Type: application/json' \ + --data '{"jsonrpc":"2.0","method":"XDPoS_getV2BlockByNumber","params":["latest"],"id":1}') + nextnum=$(echo $resp | grep -o '"Number":[0-9]*' | cut -d':' -f2 | tr -d ' ') + echo $nextnum + done +fi + +if [[ $nextnum > $num ]]; then + echo "subnet successfully running and mining blocks" +fi diff --git a/container-manager/src/gen/scripts/check-peer.sh b/container-manager/src/gen/scripts/check-peer.sh new file mode 100755 index 0000000..1366183 --- /dev/null +++ b/container-manager/src/gen/scripts/check-peer.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +resp=$(curl -s --location "http://localhost:8545" \ +--header 'Content-Type: application/json' \ +--data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}') +echo $resp +num_peers=$(echo $resp | grep -o '"result":"[^"]*"' | cut -d'"' -f4) +num_peers_dec=$(printf "%d\n" $num_peers) +echo "peers: $num_peers_dec" \ No newline at end of file diff --git a/container-manager/src/gen/scripts/csc.sh b/container-manager/src/gen/scripts/csc.sh new file mode 100755 index 0000000..acec461 --- /dev/null +++ b/container-manager/src/gen/scripts/csc.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +cd "$(dirname "$0")" + +if [[ -z $1 ]] || [[ -z $2 ]]; then + echo "Missing argument" + echo "Usage: csc.sh VERSION MODE" + echo "Version: latest or check documentation for available versions" + echo "Mode: lite, full, or reverse" + exit +fi + + +docker pull xinfinorg/csc:$1 +docker run --env-file ../contract_deploy.env --network generated_docker_net xinfinorg/csc:$1 $2 + diff --git a/container-manager/src/gen/scripts/docker-down.sh b/container-manager/src/gen/scripts/docker-down.sh new file mode 100755 index 0000000..e7d6c2b --- /dev/null +++ b/container-manager/src/gen/scripts/docker-down.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +cd "$(dirname "$0")" + + +if [[ -z $1 ]]; then + echo "Missing argument" + echo "Usage: docker-down.sh PROFILE" + echo "Profile:" + echo " machine1 - subnet nodes" + echo " services - relayer, backend, frontend" + exit +fi + +docker compose --profile $1 down + diff --git a/container-manager/src/gen/scripts/docker-up.sh b/container-manager/src/gen/scripts/docker-up.sh new file mode 100755 index 0000000..f0f3801 --- /dev/null +++ b/container-manager/src/gen/scripts/docker-up.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +cd "$(dirname "$0")" + +if [[ -z $1 ]]; then + echo "Missing argument" + echo "Usage: docker-up.sh PROFILE" + echo "Profile:" + echo " machine1 - subnet nodes" + echo " services - relayer, backend, frontend" + exit +fi + +docker compose --env-file --profile $1 pull +docker compose --env-file --profile $1 up -d + diff --git a/container-manager/src/gen/scripts/faucet-server.sh b/container-manager/src/gen/scripts/faucet-server.sh new file mode 100755 index 0000000..63a0b17 --- /dev/null +++ b/container-manager/src/gen/scripts/faucet-server.sh @@ -0,0 +1,15 @@ +#!/bin/bash +VERSION_GENERATOR="v1.0.0" + +cd "$(dirname "$0")" +cd .. + +read -p "Input Grandmaster PK (or any source wallet with funds): " SOURCE_PK + +extra="" +if [[ "$OSTYPE" == "darwin"* ]]; then + extra="--network generated_docker_net" +fi + +docker pull xinfinorg/subnet-generator:$VERSION_GENERATOR +docker run --env-file common.env --entrypoint node -p 5211:5211 $extra xinfinorg/subnet-generator:$VERSION_GENERATOR /app/src/faucet.js server $SOURCE_PK diff --git a/container-manager/src/gen/scripts/faucet.sh b/container-manager/src/gen/scripts/faucet.sh new file mode 100755 index 0000000..a50ffc9 --- /dev/null +++ b/container-manager/src/gen/scripts/faucet.sh @@ -0,0 +1,17 @@ +#!/bin/bash +VERSION_GENERATOR="v1.0.0" + +cd "$(dirname "$0")" +cd .. + +read -p "Input Grandmaster PK (or any source wallet with funds): " SOURCE_PK +read -p "Input destination wallet address: " DEST_ADDR +read -p "Input transfer amount: " AMOUNT + +extra="" +if [[ "$OSTYPE" == "darwin"* ]]; then + extra="--network generated_docker_net" +fi + +docker pull xinfinorg/subnet-generator:$VERSION_GENERATOR +docker run --env-file common.env --entrypoint node $extra xinfinorg/subnet-generator:$VERSION_GENERATOR /app/src/faucet.js $SOURCE_PK $DEST_ADDR $AMOUNT \ No newline at end of file diff --git a/container-manager/src/gen/scripts/generate.sh b/container-manager/src/gen/scripts/generate.sh new file mode 100755 index 0000000..2214f11 --- /dev/null +++ b/container-manager/src/gen/scripts/generate.sh @@ -0,0 +1,27 @@ +#!/bin/bash +VERSION_GENERATOR="v1.1.0" +VERSION_GENESIS="v0.3.1" + +current_dir="$(cd "$(dirname "$0")" && pwd)" + +echo 'pull docker images' +docker pull xinfinorg/subnet-generator:$VERSION_GENERATOR +docker pull xinfinorg/xdcsubnets:$VERSION_GENESIS + + +echo '' +echo 'go to http://localhost:5210 to access Subnet Configuration Generator UI' +echo 'or use ssh tunnel if this is running on your server (if using VSCode to SSH, the Remote Explorer will automatically deploy this tunnel)' +echo 'ssh -N -L localhost:5210:localhost:5210 @ -i ' +mkdir -p generated/scripts +docker run -p 5210:5210 -v $current_dir/generated:/app/generated xinfinorg/subnet-generator:$VERSION_GENERATOR + + +echo 'generating genesis.json' +docker run -v $current_dir/generated/:/app/generated/ --entrypoint 'bash' xinfinorg/xdcsubnets:$VERSION_GENESIS /work/puppeth.sh || pup_success=false +if [[ $pup_success == false ]]; then + echo 'genesis.json generation failed' + exit 1 +fi + +echo 'subnet generation successful' diff --git a/container-manager/src/gen/scripts/upgrade.sh b/container-manager/src/gen/scripts/upgrade.sh new file mode 100755 index 0000000..62b7e0a --- /dev/null +++ b/container-manager/src/gen/scripts/upgrade.sh @@ -0,0 +1 @@ +wip \ No newline at end of file diff --git a/container-manager/src/libs/config.js b/container-manager/src/libs/config.js new file mode 100644 index 0000000..9ec6506 --- /dev/null +++ b/container-manager/src/libs/config.js @@ -0,0 +1,59 @@ +const dotenv = require("dotenv"); +const fs = require("fs"); +const yaml = require("js-yaml"); +const path = require("path"); +const mountPath = path.join(__dirname, "../../mount/generated/"); +const configModule = require("../gen/config_gen.js"); +const version = configModule.config.version; +const config = {}; + +module.exports = config; + +initModule(); + +function initModule() { + const [found, name] = checkMountPath(); + if (!found) { + throw Error(`Incomplete mount, did not find: ${name}`); + } + config["compose"] = getComposeCommand(); + + config["hostPath"] = process.env.HOSTPWD || ""; + if (config["hostPath"] === "") { + throw Error("Incomplete container start, did not find env: HOSTPWD"); + } + console.log("init with versions", version); + config["version"] = version; +} + +function checkMountPath() { + const shouldExistMount = path.join(mountPath, "scripts"); + if (!fs.existsSync(shouldExistMount)) { + //first check folder exists + return [false, shouldExistMount]; + } + return [true, ""]; +} + +function getComposeCommand() { + return "docker compose"; //bypass TODO: + let command = ""; + let output1 = ""; + let output2 = ""; + try { + output1 = executeCommandSync("docker compose version"); + } catch (error) {} + try { + output2 = executeCommandSync("docker-compose version"); + } catch (error) {} + if (output1 && output1.includes("version")) { + return "docker compose"; + } + if (output2 && output2.includes("version")) { + return "docker-compose"; + } + console.log("Invalid docker compose version"); + console.log("docker compose version output:", output1); + console.log("docker-compose version output:", output2); + throw Error("Invalid docker compose version"); +} diff --git a/container-manager/src/libs/docker.js b/container-manager/src/libs/docker.js new file mode 100644 index 0000000..4b9f9ae --- /dev/null +++ b/container-manager/src/libs/docker.js @@ -0,0 +1,28 @@ +// const { execSync } = require('child_process'); +// const fs = require('fs') +// const path = require('path'); +// const axios = require('axios'); +// const mountPath = path.join(__dirname, '../mount/generated/') +// const instance = axios.create({ +// socketPath: '/var/run/docker.sock', +// baseURL: "http://unix:/", +// }); + +// module.exports = { +// initModule, +// getSubnetContainers, +// testAxios, +// getContainersState, +// startComposeProfile, +// deployContract, +// stopComposeProfile, +// } + +// async function testAxios(){ +// // startSubnetNodes() +// const [subnets, services] = await getContainersState() +// return { +// subnets: subnets, +// services: services +// } +// } diff --git a/container-manager/src/libs/exec.js b/container-manager/src/libs/exec.js new file mode 100644 index 0000000..3cacc55 --- /dev/null +++ b/container-manager/src/libs/exec.js @@ -0,0 +1,359 @@ +const exec = require("child_process").exec; +const { execSync } = require("child_process"); +const path = require("path"); +const mountPath = path.join(__dirname, "../../mount/generated/"); +const config = require("./config"); +const fs = require("fs"); +const ethers = require("ethers"); + +module.exports = { + startComposeProfile, + stopComposeProfile, + deployCSC, + deployZero, + deploySubswap, + executeTest, + startSubnetGradual, + removeSubnet, + generate, + processTransfer, +}; + +const streamExecOutput = (data) => { + console.log(data); +}; + +const doneExec = () => { + console.log("Execute Done!!"); +}; + +async function startComposeProfile(profile, callbacks) { + await execute( + `${config.compose} --profile ${profile} pull;\n` + + `${config.compose} --profile ${profile} up -d;\n`, + callbacks.dataCallback, + callbacks.doneCallback + ); + return {}; +} + +async function startSubnetGradual(profile, callbacks) { + await execute( + `${config.compose} --profile machine1 pull;\n` + + `${config.compose} --profile machine1 up -d subnet1;\n` + + `sleep 10;\n` + + `${config.compose} --profile machine1 up -d subnet2;\n` + + `sleep 10;\n` + + `${config.compose} --profile machine1 up -d subnet3;\n` + + `sleep 10;\n` + + `${config.compose} --profile machine1 up -d bootnode;\n`, + callbacks.dataCallback, + callbacks.doneCallback + ); + return {}; +} + +async function stopComposeProfile(profile, callbacks) { + await execute( + `${config.compose} --profile ${profile} down`, + callbacks.dataCallback, + callbacks.doneCallback + ); + return {}; +} + +async function removeSubnet(profile, callbacks) { + await execute( + `${config.compose} --profile ${profile} down; rm -rf xdcchain*`, + callbacks.dataCallback, + callbacks.doneCallback + ); +} + +async function deployCSC(mode, callbacks) { + if (!["lite", "full", "reversefull"].includes(mode)) { + console.error("invalid csc mode", mode); + return {}; + } + + await execute( + `docker pull xinfinorg/csc:${config.version.csc};\n` + + `docker run -v ${config.hostPath}/:/app/cicd/mount/ --network docker_net xinfinorg/csc:${config.version.csc} ${mode}`, + callbacks.dataCallback, + callbacks.doneCallback + ); + + return {}; +} + +async function deploySubswap(callbacks) { + await execute( + `docker pull xinfinorg/xdc-zero:${config.version.zero};\n` + + `docker run -v ${config.hostPath}/:/app/cicd/mount/ --network docker_net xinfinorg/xdc-zero:${config.version.zero} zeroandsubswap`, + callbacks.dataCallback, + callbacks.doneCallback + ); + + return {}; +} + +async function deployZero(callbacks) { + await execute( + `docker pull xinfinorg/xdc-zero:${config.version.zero};\n` + + `docker run -v ${config.hostPath}/:/app/cicd/mount/ --network docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain`, + callbacks.dataCallback, + callbacks.doneCallback + ); + + return {}; +} + +async function executeTest(command, outputHandler, doneHandler) { + command = + "echo $RANDOM; sleep 1; echo $RANDOM; sleep 1; echo a; sleep 1; echo b; sleep 1; echo c; sleep 1; echo d; sleep 1; echo e; sleep 1; echo f"; + execute(command, outputHandler, doneHandler); +} + +async function execute(command, outputHandler, doneHandler) { + console.log("executing", command); + const pathCommand = + `cd ${mountPath};\n` + `export PWD=${config.hostPath};\n` + `${command}`; + + const prom = new Promise((resolve, reject) => { + outputHandler(pathCommand); + const execProcess = exec(pathCommand); + + execProcess.stdout.on("data", (data) => { + outputHandler(data.toString()); + // process.stdout.write(data.toString()); + }); + execProcess.stderr.on("data", (data) => { + console.error(data.toString()); + outputHandler(data.toString()); + }); + execProcess.on("error", (err) => { + console.error("Failed to start exec process:", err.message); + doneHandler(); + reject(err); // Reject the Promise on error + }); + execProcess.on("close", (code) => { + console.log(`Exec process exited with code ${code}`); + doneHandler(); + resolve(code); // Resolve the Promise on close + }); + }); + + return prom; +} + +function generate(params) { + genEnv = genGenEnv(params); + fs.writeFileSync(path.join(mountPath, "gen.env"), genEnv, (err) => { + //write to mount + if (err) { + console.error(err); + exit(); + } + }); + + let command = `cd ${__dirname}/../gen; node gen.js`; + console.log(command); + const [result, out] = callExec(command); + if (!result) { + return [result, out]; + } + console.log("gen success"); + command = `cd ${mountPath}; docker run -v ${config.hostPath}:/app/generated/ --entrypoint 'bash' xinfinorg/xdcsubnets:${config.version.genesis} /work/puppeth.sh`; + console.log(command); + const [result2, out2] = callExec(command); + return [result2, out2]; +} + +function callExec(command) { + try { + const stdout = execSync(command, { timeout: 200000, encoding: "utf-8" }); + output = stdout.toString(); + + // console.log(output); + return [true, output]; + } catch (error) { + console.log(error); + return [false, error.stdout]; + throw Error(error.stdout); + } +} + +function genGenEnv(input) { + console.log(input); + + let content_machine = ""; + if (input["text-num-machine"] > 1) { + content_machine += `\nMAIN_IP=${input["text-private-ip"]}`; + if (input["text-public-ip"] != "") { + content_machine += `\nPUBLIC_IP=${input["text-public-ip"]}`; + } + if (input["text-num-machine"] != "") { + content_machine += `\nNUM_MACHINE=${input["text-num-machine"]}`; + } + } else { + content_machine += `\nMAIN_IP=127.0.0.1`; + content_machine += `\nNUM_MACHINE=1`; + } + + let parentnet = ""; + switch (input.pnradio) { + case "pn-radio-testnet": + parentnet = "testnet"; + break; + case "pn-radio-devnet": + parentnet = "devnet"; + break; + case "pn-radio-mainnet": + parentnet = "mainnet"; + break; + } + + let relayer_mode = ""; + switch (input.rmradio) { + case "rm-radio-full": + relayer_mode = "full"; + break; + case "rm-radio-lite": + relayer_mode = "lite"; + break; + } + + let content_custom_key = ""; + if (input["grandmaster-pk"] != "") { + content_custom_key += `\nGRANDMASTER_PK=${input["grandmaster-pk"]}`; + } + + let subnet_keys = []; + let idx = 1; + while ("subnet-key" + idx.toString() in input) { + key = "subnet-key" + idx.toString(); + subnet_keys.push(input[key]); + idx++; + } + if (subnet_keys.length > 0) { + key_string = subnet_keys.join(","); + content_custom_key += `\nSUBNETS_PK=${key_string}`; + } + + let content_version = ""; + if (input["customversion-subnet"] != "") { + content_version += `\nVERSION_SUBNET=${input["customversion-subnet"]}`; + } + if (input["customversion-bootnode"] != "") { + content_version += `\nVERSION_BOOTNODE=${input["customversion-bootnode"]}`; + } + if (input["customversion-relayer"] != "") { + content_version += `\nVERSION_RELAYER=${input["customversion-relayer"]}`; + } + if (input["customversion-stats"] != "") { + content_version += `\nVERSION_STATS=${input["customversion-stats"]}`; + } + if (input["customversion-frontend"] != "") { + content_version += `\nVERSION_FRONTEND=${input["customversion-frontend"]}`; + } + if (input["customversion-csc"] != "") { + content_version += `\nVERSION_CSC=${input["customversion-csc"]}`; + } + if (input["customversion-zero"] != "") { + content_version += `\nVERSION_ZERO=${input["customversion-zero"]}`; + } + + let content_zero = ""; + if (relayer_mode == "full" && "xdczero-checkbox" in input) { + if (input["zmradio"] == "zm-radio-one") { + content_zero += "\nXDC_ZERO=one-directional"; + } + if (input["zmradio"] == "zm-radio-bi") { + content_zero += "\nXDC_ZERO=bi-directional"; + content_zero += `\nSUBNET_WALLET_PK=${input["subnet-wallet-pk"]}`; + content_zero += `\nSUBNET_ZERO_WALLET_PK=${input["subnet-zero-wallet-pk"]}`; + } + content_zero += `\nPARENTNET_ZERO_WALLET_PK=${input["parentnet-zero-wallet-pk"]}`; + if ("subswap-checkbox" in input) { + content_zero += "\nSUBSWAP=true"; + } + } + + content = ` +NETWORK_NAME=${input["text-subnet-name"]} +NUM_SUBNET=${input["text-num-subnet"]} +PARENTNET=${parentnet} +PARENTNET_WALLET_PK=${input["parentnet-wallet-pk"]} +RELAYER_MODE=${relayer_mode} +`; + content += content_machine; + content += "\n"; + content += content_custom_key; + content += "\n"; + content += content_version; + content += "\n"; + content += content_zero; + + console.log(content); + + return content; +} + +async function processTransfer(provider, fromWallet, toAddress, amount) { + // fromPK = inputs[2]; + // toAddress = inputs[3]; + // amount = inputs[4]; + // const provider = new ethers.JsonRpcProvider(subnetURL); + // const fromWallet = new ethers.Wallet(fromPK, provider); + let tx = { + to: toAddress, + value: ethers.parseEther(amount), + }; + + try { + await provider._detectNetwork(); + } catch (error) { + throw Error("Cannot connect to RPC"); + } + + let sendPromise = fromWallet.sendTransaction(tx); + txHash = await sendPromise.then((tx) => { + return tx.hash; + }); + console.log("TX submitted, confirming TX execution, txhash:", txHash); + + let receipt; + let count = 0; + while (!receipt) { + count++; + // console.log("tx receipt check loop", count); + if (count > 60) { + throw Error("Timeout: transaction did not execute after 60 seconds"); + } + await sleep(1000); + let receipt = await provider.getTransactionReceipt(txHash); + if (receipt && receipt.status == 1) { + console.log("Successfully transferred", amount, "subnet token"); + let fromBalance = await provider.getBalance(fromWallet.address); + fromBalance = ethers.formatEther(fromBalance); + let toBalance = await provider.getBalance(toAddress); + toBalance = ethers.formatEther(toBalance); + console.log("Current balance"); + console.log(`${fromWallet.address}: ${fromBalance}`); + console.log(`${toAddress}: ${toBalance}`); + return { + fromBalance: fromBalance, + toBalance: toBalance, + txHash: txHash, + }; + } + } +} + +function findENVInFile(env, filepath) { + const envFileContent = fs.readFileSync(filepath, "utf8"); + const regex = new RegExp(`^${env}=.*`, "gm"); + let matches = envFileContent.match(regex); + matches = matches === null ? [] : matches; + return matches; +} diff --git a/container-manager/src/libs/state.js b/container-manager/src/libs/state.js new file mode 100644 index 0000000..9046c39 --- /dev/null +++ b/container-manager/src/libs/state.js @@ -0,0 +1,453 @@ +const axios = require("axios"); +const { ethers } = require("ethers"); +const { getAccountPath } = require("ethers"); +const instance = axios.create({ + socketPath: "/var/run/docker.sock", + baseURL: "http://unix:/", +}); +const fs = require("fs"); +const path = require("path"); +const { env } = require("process"); +const mountPath = path.join(__dirname, "../../mount/generated/"); + +module.exports = { + getState, + getSubnetContainers, + getContainersState, + checkMining, + getFaucetParams, +}; + +const stateGen = { + NONE: "NONE", + INCOMPLETE: "INCOMPLETE", + GENERATED: "GENERATED", + COMPLETED: "COMPLETED", +}; + +async function getState() { + const [deployState, requireContracts] = getStateGen(); + const containers = await getContainersState(); + const mineInfo = await checkMining(); + + return { + containers: containers, + mineInfo: mineInfo, + deployState: deployState, + requirements: requireContracts, + }; +} + +async function getSubnetContainers() { + const response = await instance.get("http://localhost/containers/json"); + const containers = response.data; + const filtered = []; + for (let i = 0; i < containers.length; i++) { + if (containers[i].Names[0].includes("generated")) { + const c = { + name: containers[i].Names[0].substring(1), + image: containers[i].Image, + state: containers[i].State, + status: containers[i].Status, + }; + const networkName = containers[i].HostConfig.NetworkMode; + const ip = + containers[i].NetworkSettings.Networks[networkName].IPAMConfig + .IPv4Address; + const rpcPort = extractRPCPort(c.name); + c.network = networkName; + c.ip = ip; + c.rpcPort = rpcPort; + filtered.push(c); + } + } + return filtered; +} + +async function getContainersState() { + const containers = await getSubnetContainers(); + const subnets = []; + const services = []; + const subswap = []; + const explorer = []; + const others = []; + for (let i = 0; i < containers.length; i++) { + const [isSubnet, nameSubnet] = isSubnetContainer(containers[i].name); + const [isService, nameService] = isServiceContainer(containers[i].name); + const isSubswap = isSubswapContainer(containers[i].name); + const isExplorer = isExplorerContainer(containers[i].name); + if (isSubnet) { + subnets.push({ + name: nameSubnet, + state: containers[i].state, + }); + } else if (isService) { + services.push({ + name: nameService, + state: containers[i].state, + }); + } else if (isSubswap) { + subswap.push({ + name: nameService, + state: containers[i].state, + }); + } else if (isExplorer) { + explorer.push({ + name: nameService, + state: containers[i].state, + }); + } else { + others.push({ + name: nameService, + state: containers[i].state, + }); + } + } + + return { + subnets: subnets, + services: services, + subswap: subswap, + explorer: explorer, + others: others, + }; +} + +async function checkDeployState() { + //maybe too similar to getcontainersstate (already displayed) +} + +async function checkContractState() { + // csc = lite/full, deployed/notfound, (current height?) + // reverse csc = deployed/notfound + // subswap = deployed/notfound +} + +async function checkMining() { + const containers = await getSubnetContainers(); + const blockHeights = []; + const peerCounts = []; + for (let i = 0; i < containers.length; i++) { + const c = containers[i]; + if (c.name.includes("subnet") && c.state == "running") { + blockHeights.push(await checkBlock(c.ip, c.rpcPort)); + peerCounts.push(await checkPeers(c.ip, c.rpcPort)); + } + } + return { + blocks: blockHeights, + peers: peerCounts, + }; +} + +async function checkBlock(containerIP, containerPort) { + let url = `http://${containerIP}:${containerPort}`; + const data = { + jsonrpc: "2.0", + method: "XDPoS_getV2BlockByNumber", + params: ["latest"], + id: 1, + }; + const headers = { + "Content-Type": "application/json", + }; + + try { + let response; + try { + response = await axios.post(url, data, { headers, timeout: 2000 }); + } catch (error) { + url = `http://localhost:${containerPort}`; //fallback for local testing + response = await axios.post(url, data, { headers, timeout: 2000 }); + } + let block = response.data.result.Number; + if (block == null) block = 0; + return block; + } catch (error) { + console.log(error.code); + } +} + +async function checkPeers(containerIP, containerPort) { + let url = `http://${containerIP}:${containerPort}`; + const data = { + jsonrpc: "2.0", + method: "net_peerCount", + id: 1, + }; + const headers = { + "Content-Type": "application/json", + }; + + try { + let response; + try { + response = await axios.post(url, data, { headers, timeout: 2000 }); + } catch (error) { + url = `http://localhost:${containerPort}`; //fallback for local testing + response = await axios.post(url, data, { headers, timeout: 2000 }); + } + const peerHex = response.data.result; + const peerCount = parseInt(peerHex, 16); + return peerCount; + } catch (error) { + console.error(error.code); + } +} + +function confirmCompatible() { + //check docker version + //check docker compose version + // only requirement is docker + // + // for init +} + +function isSubnetContainer(container) { + container = container.split("-"); //container name format: generated-xxxxx-1, need to extract middle string + container.pop(); + container.shift(); + const name = container.join(); + let isSubnet = false; + if (name.includes("subnet") || name.includes("bootnode")) { + isSubnet = true; + } + return [isSubnet, name]; +} + +function isServiceContainer(container) { + container = container.split("-"); //container name format: generated-xxxxx-1, need to extract middle string + container.pop(); + container.shift(); + const name = container.join(); + let isService = false; + if (name.includes("subswap_frontend")) return [false, name]; + if ( + name.includes("stats") || + name.includes("frontend") || + name.includes("relayer") || + name.includes("bootnode") + ) { + isService = true; + } + return [isService, name]; +} + +function isSubswapContainer(container) { + container = container.split("-"); //container name format: generated-xxxxx-1, need to extract middle string + container.pop(); + container.shift(); + const name = container.join(); + if (name.includes("subswap_frontend")) { + return true; + } else { + return false; + } +} + +function isExplorerContainer(container) { + container = container.split("-"); //container name format: generated-xxxxx-1, need to extract middle string + container.pop(); + container.shift(); + const name = container.join(); + if (name.includes("explorer")) { + return true; + } else { + return false; + } +} + +function extractRPCPort(name) { + const shortName = name.split("-")[1]; + nodeNum = parseInt(shortName.replace("subnet", "")); + // if (nodeNum === null) { + // return 9999; + // } + return 8545 + nodeNum - 1; +} + +function sleepSync(ms) { + const start = Date.now(); + while (Date.now() - start < ms) { + // Busy-wait loop (blocks the event loop) + } +} + +function getStateGen() { + files = [ + "gen.env", + "docker-compose.yml", + "common.env", + "contract_deploy.env", + "genesis.json", + ]; + let count = 0; + for (let i = 0; i < files.length; i++) { + filename = path.join(mountPath, files[i]); + if (fs.existsSync(filename)) count++; + } + + if (count == 0) return [stateGen.NONE, null]; + if (count < files.length) return [stateGen.INCOMPLETE, null]; + + const req = readContractRequirement(); + const addresses = readAddressInfo(); + const contracts = readDeployedContracts(); + const subnetConfig = readConfig(); + const details = { + requireContracts: req, + deployedContracts: contracts, + addresses: addresses, + subnetConfig: subnetConfig, + }; + if (isContractDeployComplete(req)) { + return [stateGen.COMPLETED, details]; + } else { + return [stateGen.GENERATED, details]; + } +} + +function pkToAddress(pk) { + try { + const privateKey = pk.split("=")[1]; + const wallet = new ethers.Wallet(privateKey); + const address = wallet.address; + return address; + } catch (error) { + return ""; + } +} + +function readContractRequirement() { + const filepath = path.join(mountPath, "gen.env"); + if (!fs.existsSync(filepath)) return {}; + const relayerMode = findENVInFile("RELAYER_MODE", filepath); + const zeroMode = findENVInFile("XDC_ZERO", filepath); + const subswap = findENVInFile("SUBSWAP", filepath); + + const req = {}; + if (relayerMode.length != 0) { + req["relayer"] = relayerMode[0].split("=")[1]; + } + if (zeroMode.length != 0) { + req["zero"] = zeroMode[0].split("=")[1]; + } + if (subswap.length != 0) { + req["subswap"] = subswap[0].split("=")[1]; + } + return req; +} + +function readAddressInfo() { + const filepath = path.join(mountPath, "gen.env"); + if (!fs.existsSync(filepath)) return {}; + let parentnetWallet = findENVInFile("PARENTNET_WALLET_PK", filepath); + parentnetWallet = + parentnetWallet.length > 0 ? pkToAddress(parentnetWallet[0]) : ""; + let parentnetZeroWallet = findENVInFile("PARENTNET_ZERO_WALLET_PK", filepath); + parentnetZeroWallet = + parentnetZeroWallet.length > 0 ? pkToAddress(parentnetZeroWallet[0]) : ""; + let subnetWallet = findENVInFile("SUBNET_WALLET_PK", filepath); + subnetWallet = subnetWallet.length > 0 ? pkToAddress(subnetWallet[0]) : ""; + let subnetZeroWallet = findENVInFile("SUBNET_ZERO_WALLET_PK", filepath); + subnetZeroWallet = + subnetZeroWallet.length > 0 ? pkToAddress(subnetZeroWallet[0]) : ""; + + return { + parentnetWallet: parentnetWallet, + parentnetZeroWallet: parentnetZeroWallet, + subnetWallet: subnetWallet, + subnetZeroWallet: subnetZeroWallet, + }; +} + +function readDeployedContracts() { + const filepath = path.join(mountPath, "common.env"); + if (!fs.existsSync(filepath)) return {}; + + let csc = findENVInFile("CHECKPOINT_CONTRACT", filepath); + csc = csc.length > 0 ? csc[0].split("=")[1] : ""; + let reverseCsc = findENVInFile("REVERSE_CHECKPOINT_CONTRACT", filepath); + reverseCsc = reverseCsc.length > 0 ? reverseCsc[0].split("=")[1] : ""; + let zeroContract = findENVInFile("PARENTNET_ZERO_CONTRACT", filepath); //checkname + zeroContract = zeroContract.length > 0 ? zeroContract[0].split("=")[1] : ""; + let reverseZeroContract = findENVInFile("SUBNET_ZERO_CONTRACT", filepath); //checkname + reverseZeroContract = + reverseZeroContract.length > 0 ? reverseZeroContract[0].split("=")[1] : ""; + let parentnetApp = findENVInFile("PARENTNET_APP", filepath); + parentnetApp = parentnetApp.length > 0 ? parentnetApp[0].split("=")[1] : ""; + let subnetApp = findENVInFile("SUBNET_APP", filepath); + subnetApp = subnetApp.length > 0 ? subnetApp[0].split("=")[1] : ""; + + return { + csc: csc, + reverseCsc: reverseCsc, + zeroContract: zeroContract, + reverseZeroContract: reverseZeroContract, + parentnetApp: parentnetApp, + subnetApp: subnetApp, + }; +} + +function readConfig() { + const filepath = path.join(mountPath, "gen.env"); + if (!fs.existsSync(filepath)) return {}; + + let networkName = findENVInFile("NETWORK_NAME", filepath); + networkName = networkName.length > 0 ? networkName[0].split("=")[1] : ""; + let numSubnet = findENVInFile("NUM_SUBNET", filepath); + numSubnet = numSubnet.length > 0 ? numSubnet[0].split("=")[1] : ""; + let numMachine = findENVInFile("NUM_MACHINE", filepath); + numMachine = numMachine.length > 0 ? numMachine[0].split("=")[1] : ""; + let parentnet = findENVInFile("PARENTNET", filepath); + parentnet = parentnet.length > 0 ? parentnet[0].split("=")[1] : ""; + + return { + networkName: networkName, + numSubnet: numSubnet, + numMachine: numMachine, + parentnet: parentnet, + }; +} + +function isContractDeployComplete(req) { + const filepath = path.join(mountPath, "common.env"); + if ("relayer" in req) { + const relayer = findENVInFile("CHECKPOINT_CONTRACT", filepath); + if (relayer.length == 0) return false; + } + + if ("zero" in req) { + const parentZero = findENVInFile("PARENTNET_ZERO_CONTRACT", filepath); + const subnetZero = findENVInFile("SUBNET_ZERO_CONTRACT", filepath); + if (parentZero.length == 0 && subnetZero.length == 0) return false; + } + + if ("subswap" in req) { + const parentSubswap = findENVInFile("PARENTNET_APP", filepath); + const subnetSubswap = findENVInFile("SUBNET_APP", filepath); + if (parentSubswap.length == 0 && subnetSubswap.length == 0) return false; + } + + return true; +} + +function findENVInFile(env, filepath) { + const envFileContent = fs.readFileSync(filepath, "utf8"); + const regex = new RegExp(`^${env}=.*`, "gm"); + let matches = envFileContent.match(regex); + matches = matches === null ? [] : matches; + return matches; +} + +function getFaucetParams() { + const keysFile = fs.readFileSync(path.join(mountPath, "keys.json"), "utf8"); + const gmKey = JSON.parse(keysFile).Grandmaster.PrivateKey; + const commonPath = path.join(mountPath, "common.env"); + const url = findENVInFile("SUBNET_URL", commonPath)[0].split("=")[1]; + + return { + subnetUrl: url, + gmKey: gmKey, + }; +} diff --git a/container-manager/src/nodemon.json b/container-manager/src/nodemon.json new file mode 100644 index 0000000..76fb540 --- /dev/null +++ b/container-manager/src/nodemon.json @@ -0,0 +1,4 @@ +{ + "ext": "js,html,css", + "ignore": ["node_modules/"] +} diff --git a/container-manager/src/package.json b/container-manager/src/package.json new file mode 100644 index 0000000..078251f --- /dev/null +++ b/container-manager/src/package.json @@ -0,0 +1,24 @@ +{ + "name": "docker-api-tester", + "version": "1.0.0", + "description": "A simple Node.js app to test Docker API access", + "main": "test-docker.js", + "scripts": { + "start": "node express.js", + "dev": "HOSTPWD=$PWD/../mount/generated nodemon express.js" + }, + "dependencies": { + "axios": "^1.7.9", + "consolidate": "^1.0.4", + "dockerode": "^3.3.4", + "dotenv": "^16.4.7", + "ethers": "^6.13.5", + "express": "^4.21.2", + "js-yaml": "^4.1.0", + "pug": "^3.0.3", + "swig": "^1.4.2" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } +} diff --git a/container-manager/src/public/script.js b/container-manager/src/public/script.js new file mode 100644 index 0000000..3bd8023 --- /dev/null +++ b/container-manager/src/public/script.js @@ -0,0 +1,433 @@ +let prevBlockNum = 0; +let prevPrevBlockNum = 0; + +async function callStateApi(route, outElementId) { + const outputDiv = document.getElementById(outElementId); + try { + const response = await fetch(route, { method: "GET" }); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + const contentType = response.headers.get("Content-Type"); + let data; + if (contentType && contentType.includes("application/json")) { + data = await response.json(); + adjustStateDivs(data); + display = data; + display = JSON.stringify(display, null, 2); + } else { + data = await response.text(); + } + + outputDiv.textContent = display; + } catch (error) { + console.error("Error:", error); + outputDiv.textContent = "API call failed: " + error.message; + } +} + +async function generate1(network, subswap) { + console.log("generate1"); + loadingStart(); + const parentnetWallet = await genAddress(); + const parentnetZeroWallet = await genAddress(); + const subnetWallet = await genAddress(); + const subnetZeroWallet = await genAddress(); + const formData = { + "text-subnet-name": "myxdcsubnet", + "text-num-subnet": "1", + "text-num-machine": "1", + "text-private-ip": "", + "text-public-ip": "", + "grandmaster-pk": "", + "customversion-subnet": "", + "customversion-bootnode": "", + "customversion-relayer": "", + "customversion-stats": "", + "customversion-frontend": "", + "customversion-csc": "", + "customversion-zero": "", + pnradio: `pn-radio-${network}`, + "parentnet-wallet-pk": parentnetWallet.privateKey, + rmradio: "rm-radio-full", + "parentnet-zero-wallet-pk": "", + zmradio: "zm-radio-one", + "subnet-wallet-pk": "", + "subnet-zero-wallet-pk": "", + }; + if (subswap) { + formData["xdczero-checkbox"] = "on"; + formData["parentnet-zero-wallet-pk"] = parentnetZeroWallet.privateKey; + formData["zmradio"] = "zm-radio-bi"; + formData["subnet-wallet-pk"] = subnetWallet.privateKey; + formData["subnet-zero-wallet-pk"] = subnetZeroWallet.privateKey; + formData["subswap-checkbox"] = "on"; + } + const response = await fetch("/submit_preconfig", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + const outdiv = document.getElementById("output1"); + outdiv.textContent = await response.text(); +} + +async function generate3(network, subswap) { + console.log("generate3"); + loadingStart(); + const parentnetWallet = await genAddress(); + const parentnetZeroWallet = await genAddress(); + const subnetWallet = await genAddress(); + const subnetZeroWallet = await genAddress(); + const formData = { + "text-subnet-name": "myxdcsubnet", + "text-num-subnet": "3", + "text-num-machine": "1", + "text-private-ip": "", + "text-public-ip": "", + "grandmaster-pk": "", + "customversion-subnet": "", + "customversion-bootnode": "", + "customversion-relayer": "", + "customversion-stats": "", + "customversion-frontend": "", + "customversion-csc": "", + "customversion-zero": "", + pnradio: `pn-radio-${network}`, + "parentnet-wallet-pk": parentnetWallet.privateKey, + rmradio: "rm-radio-full", + }; + if (subswap) { + formData["xdczero-checkbox"] = "on"; + formData["parentnet-zero-wallet-pk"] = parentnetZeroWallet.privateKey; + formData["zmradio"] = "zm-radio-bi"; + formData["subnet-wallet-pk"] = subnetWallet.privateKey; + formData["subnet-zero-wallet-pk"] = subnetZeroWallet.privateKey; + formData["subswap-checkbox"] = "on"; + } + const response = await fetch("/submit_preconfig", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + const outdiv = document.getElementById("output1"); + outdiv.textContent = await response.text(); +} + +async function genAddress() { + const response = await fetch("/address", { method: "GET" }); + return response.json(); +} + +async function callStreamApi(route) { + // const outputElement = document.createElement('div'); + // outputElement.className = 'output'; + loadingStart(); + collapseHistoryDivs(); + const [outputWrapper, outputElement] = createCollapsibleDiv(route); + document.getElementById("history-text").textContent = "History:"; + document.getElementById("history").appendChild(outputWrapper); + + // const outputElement = document.getElementById("output"); + const eventSource = new EventSource(route); + try { + outputElement.textContent = ""; + eventSource.onmessage = (event) => { + outputElement.textContent += event.data + "\n"; + outputElement.scrollTop = outputElement.scrollHeight; + }; + + eventSource.addEventListener("close", (event) => { + outputElement.textContent += event.data + "\n"; + eventSource.close(); + }); + + eventSource.onerror = () => { + console.log("EventSource failed."); + outputElement.textContent += "Error: Connection lost.\n"; + eventSource.close(); + }; + } catch (error) { + console.error("Error:", error); + outputElement.textContent = "API call failed: " + error.message; + } +} + +function createCollapsibleDiv(route) { + const newDiv = document.createElement("div"); + newDiv.className = "output-wrapper"; + + const buttonDiv = document.createElement("div"); + const command = document.createElement("button"); + command.textContent = route; + const toggleButton = document.createElement("button"); + toggleButton.className = "toggle-button"; + toggleButton.textContent = "Collapse"; + buttonDiv.appendChild(command); + buttonDiv.appendChild(document.createTextNode(" ")); + buttonDiv.appendChild(toggleButton); + newDiv.appendChild(buttonDiv); + + const contentDiv = document.createElement("div"); + contentDiv.className = "output"; + contentDiv.style.display = "block"; + newDiv.appendChild(contentDiv); + + // Add click event to the toggle button + toggleButton.addEventListener("click", function () { + if (contentDiv.style.display === "none") { + contentDiv.style.display = "block"; + toggleButton.textContent = "Collapse"; + } else { + contentDiv.style.display = "none"; + toggleButton.textContent = "Expand"; + } + }); + + return [newDiv, contentDiv]; +} + +function collapseHistoryDivs() { + const outputElements = document.getElementsByClassName("output"); + for (let element of outputElements) { + element.style.display = "none"; + } + + const toggleButtonElements = document.getElementsByClassName("toggle-button"); + for (let element of toggleButtonElements) { + element.textContent = "Expand"; + } +} + +function adjustStateDivs(data) { + if (data.deployState != "NONE") { + disableButtons("gen-button"); + checkSubnetStarted(data.containers.subnets); + checkMiningState(data.mineInfo); + checkServicesStarted(data.containers.services); + checkSubswapFrontendStarted(data.containers.subswap); + // checkExplorerStarted(data.containers.explorer); + showAddresses(data.requirements.addresses); + showCopyInstruction(data.requirements.subnetConfig); + showFaucet(data.requirements); + unhideContractButtons(data.requirements.requireContracts); + disableContractButtons(data.requirements.deployedContracts); + } + + // allowClick() + loadingFinished(); +} + +function loadingFinished() { + document.getElementById("body-wrap").style.pointerEvents = "auto"; +} + +function loadingStart() { + document.getElementById("body-wrap").style.pointerEvents = "none"; + document.getElementById("state").textContent = "Status: Loading..."; +} + +function disableButtons(className) { + const elements = document.querySelectorAll(`.${className}`); + elements.forEach((element) => { + // element.style.display = 'block'; + element.disabled = true; + }); +} + +function checkSubnetStarted(subnetsContainers) { + if (subnetsContainers.length == 0) { + document.getElementById("start-subnet-button").disabled = false; + document.getElementById("stop-subnet-button").disabled = true; + } else { + document.getElementById("start-subnet-button").disabled = true; + document.getElementById("stop-subnet-button").disabled = false; + } +} + +function checkMiningState(mineInfo) { + if (!mineInfo) { + document.getElementById( + "mining-status" + ).innerHTML = `Confirm mining status: Not Mining`; + return; + } + if (mineInfo.blocks[0] > prevPrevBlockNum) { + prevBlockNum = mineInfo.blocks[0]; + prevPrevBlockNum = prevBlockNum; + document.getElementById( + "mining-status" + ).innerHTML = `Confirm mining status: Mining`; + } +} + +function checkServicesStarted(servicesContainers) { + if (servicesContainers.length == 0) { + document.getElementById("start-services-button").disabled = false; + document.getElementById("stop-services-button").disabled = true; + } else { + document.getElementById("start-services-button").disabled = true; + document.getElementById("stop-services-button").disabled = false; + const ui = new URL(window.location.href); + ui.port = "5214"; + const relayer = new URL(window.location.href); + relayer.port = "5215"; + document.getElementById("services-details").innerHTML = ` +Subnet UI (please wait 2 minutes for loading after startup) +
+Relayer UI +

+`; + } +} + +function checkSubswapFrontendStarted(containers) { + if (containers.length == 0) { + document.getElementById("start-subswap-button").disabled = false; + document.getElementById("stop-subswap-button").disabled = true; + } else { + document.getElementById("start-subswap-button").disabled = true; + document.getElementById("stop-subswap-button").disabled = false; + const subswap = new URL(window.location.href); + subswap.port = "5216"; + document.getElementById("subswap-details").innerHTML = ` +Subswap UI (please wait 2 minutes for loading after startup) +
+`; + } +} + +function checkExplorerStarted(containers) { + if (containers.length == 0) { + document.getElementById("start-explorer-button").disabled = false; + document.getElementById("stop-explorer-button").disabled = true; + } else { + document.getElementById("start-explorer-button").disabled = true; + document.getElementById("stop-explorer-button").disabled = false; + const explorer = new URL(window.location.href); + explorer.port = "5217"; + document.getElementById("explorer-details").innerHTML = ` +Explorer UI +
+`; + } +} + +function enableButtonClass(className) { + const elements = document.querySelectorAll(`.${className}`); + elements.forEach((element) => { + element.disabled = false; + }); +} + +function unhideContractButtons(contracts) { + const cscLiteButton = document.getElementById("button-csc-lite"); + const cscFullButton = document.getElementById("button-csc-full"); + const reverseCscButton = document.getElementById("button-reverse-csc"); + // const zeroButton = document.getElementById('button-zero') //retire this concept, only subswap or no subswap + const subswapButton = document.getElementById("button-subswap"); + + if (contracts.relayer == "lite") { + cscLiteButton.disabled = false; + cscLiteButton.style.display = "block"; + } + if (contracts.relayer == "full") { + cscFullButton.disabled = false; + cscFullButton.style.display = "block"; + } + if (contracts.zero == "bi-directional") { + reverseCscButton.disabled = false; + reverseCscButton.style.display = "block"; + } + if (contracts.subswap == "true") { + subswapButton.disabled = false; + subswapButton.style.display = "block"; + } +} + +function disableContractButtons(contracts) { + const cscLiteButton = document.getElementById("button-csc-lite"); + const cscFullButton = document.getElementById("button-csc-full"); + const reverseCscButton = document.getElementById("button-reverse-csc"); + const subswapButton = document.getElementById("button-subswap"); + + if (contracts.csc != "") { + cscLiteButton.disabled = true; + cscFullButton.disabled = true; + } + if (contracts.reverseCsc != "") { + reverseCscButton.disabled = true; + } + if (contracts.parentnetApp != "" && contracts.subnetApp != "") { + subswapButton.disabled = true; + } +} + +function showAddresses(addresses) { + const parentnetWallet = document.getElementById("parentnet-wallet"); + const parentnetZeroWallet = document.getElementById("parentnet-zero-wallet"); + const subnetWallet = document.getElementById("subnet-wallet"); + const subnetZeroWallet = document.getElementById("subnet-zero-wallet"); + + if (addresses.parentnetWallet !== "") { + parentnetWallet.innerHTML = + " Relayer Parentnet Wallet: " + addresses.parentnetWallet; + } else { + parentnetWallet.innerHTML = ""; + } + if (addresses.parentnetZeroWallet !== "") { + parentnetZeroWallet.innerHTML = + " Relayer Parentnet Zero Wallet: " + addresses.parentnetZeroWallet; + } else { + parentnetZeroWallet.innerHTML = ""; + } + // if (addresses.subnetWallet !== ""){ + // subnetWallet.innerHTML = ' Relayer Subnet Wallet: '+addresses.subnetWallet + // } else { + // subnetWallet.innerHTML = "" + // } + // if (addresses.subnetZeroWallet !== ""){ + // subnetZeroWallet.innerHTML = ' Relayer Subnet Zero Wallet: '+addresses.subnetZeroWallet + // } else { + // subnetZeroWallet.innerHTML = "" + // } +} + +function showCopyInstruction(config) { + if (config.numMachine != "" && parseInt(config.numMachine) > 1) { + const copyInstruction = document.getElementById("copy-instruction"); + copyInstruction.innerHTML = ` +Copy files docker-compose.yml, genesis.json, config/subnetX.env to other machines
+Then start subnet nodes on other machines:
+docker compose --profile machineX pull;
+docker compose --profile machineX up -d;
+`; + } +} + +function showFaucet(requirements) { + if (requirements.subnetConfig.parentnet == "testnet") { + const infoDiv = document.getElementById("mainnet-testnet-info"); + infoDiv.textContent = "3. Add Testnet balance to: "; + const testnetFaucetInfo = document.getElementById("testnet-faucet-info"); + testnetFaucetInfo.style.display = "block"; + } + if (requirements.subnetConfig.parentnet == "mainnet") { + const infoDiv = document.getElementById("mainnet-testnet-info"); + infoDiv.textContent = "3. Add Mainnet balance to: "; + } + // if (requirements.addresses.subnetWallet != "" || requirements.addresses.subnetZeroWallet != ""){ + // const subnetFaucetInfo = document.getElementById("subnet-faucet-info") + // subnetFaucetInfo.style.display = "block" + // } +} +async function fetchLoop() { + await callStateApi("/state", "state"); + setInterval(() => { + callStateApi("/state", "state"); + }, 5000); +} diff --git a/container-manager/src/public/styles.css b/container-manager/src/public/styles.css new file mode 100644 index 0000000..9584b31 --- /dev/null +++ b/container-manager/src/public/styles.css @@ -0,0 +1,131 @@ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + display: flex; + /* flex-direction: column; */ + justify-content: center; + align-items: center; + min-height: 100vh; /* Use min-height instead of height */ + margin: 0; + padding: 20px; /* Add padding to prevent content from touching the edges */ +} + +.body-wrap { + display: flex; + background-color: rgba(0, 0, 0, 0); + z-index: 9999; + pointer-events: none; +} + +.container { + background-color: #fff; + padding: 20px; + margin: 20px; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + text-align: center; + min-width: 300px; + min-height: 300px; +} + + +.left-aligned { + text-align: left; +} + +h1 { + color: #333; +} + +.button { + /* display: block; */ + margin: 10px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; + border: none; + border-radius: 5px; + background-color: #007bff; + color: white; +} +.button:hover { + background-color: #0056b3; +} +.button:disabled { + background-color: #cccccc; + color: #666666; + cursor: not-allowed; +} +.button.red { + background-color: #db0000; +} +.button.red:hover { + background-color: #b30000; +} +.button.contract-deploy { + display: none; +} +.toggle-button { + background-color: #007bff; + color: white; + border: none; + padding: 5px 10px; + cursor: pointer; +} +.toggle-button:hover { + background-color: #0056b3; +} + +.output { + /* display: block; */ + margin-top: 20px; + padding: 15px; + background-color: #f4f4f4; + border: 1px solid #ddd; + border-radius: 5px; + font-family: monospace; + text-align: left; + white-space: pre-wrap; /* Ensures JSON formatting is preserved */ +} +.output-status { + /* display: block; */ + margin-top: 20px; + padding: 15px; + background-color: #f4f4f4; + border: 1px solid #ddd; + border-radius: 5px; + font-family: monospace; + text-align: left; + white-space: pre-wrap; /* Ensures JSON formatting is preserved */ +} +#mining-status { + /* display: block; */ + margin: 10px; + padding: 10px 20px;; + background-color: #f4f4f4; + border: 1px solid #ddd; + border-radius: 5px; + font-family: monospace; + text-align: left; + width: 300px +} +.display-address { + margin: 10px; + padding: 10px 20px;; + background-color: #f4f4f4; + border: 1px solid #ddd; + border-radius: 5px; + font-family: monospace; + text-align: left; + width: 600px +} + + + +#testnet-faucet-info { + display: none; +} + +#subnet-faucet-info { + display: none; +} diff --git a/container-manager/src/views/debug.html b/container-manager/src/views/debug.html new file mode 100644 index 0000000..7ee600c --- /dev/null +++ b/container-manager/src/views/debug.html @@ -0,0 +1,49 @@ + + + + + + Subnet Deployment Wizard + + + +
+
+

Subnet Deployment Wizard

+
+ + + +
+
+ + + + + + +
+
+ + + +
+ +
+
History:
+
+
+ +
+

Status

+
Status:
+
+
+ + + + \ No newline at end of file diff --git a/container-manager/src/views/faucet/custom.css b/container-manager/src/views/faucet/custom.css new file mode 100644 index 0000000..e217b06 --- /dev/null +++ b/container-manager/src/views/faucet/custom.css @@ -0,0 +1,26 @@ +#myForm { + margin-left: 15%; + /* margin-right: 200px; */ + max-width: 1100px; +} + +#helper { + margin-left: 15%; + margin-top: 50px; + max-width: 1100px; +} +#helper-title { + margin-right: 10%; +} +.helper-block { + padding: 10px; +} + +#final { + margin-left: 175px; + margin-top: 20px; + margin-bottom: 40px; +} +#error { + color: red; +} diff --git a/container-manager/src/views/faucet/index.pug b/container-manager/src/views/faucet/index.pug new file mode 100644 index 0000000..2c6490e --- /dev/null +++ b/container-manager/src/views/faucet/index.pug @@ -0,0 +1,74 @@ +head + meta(charset='utf-8') + title Subnet Faucet + style + include ./pure-min.css + include ./custom.css + + +body() + center + h1 Subnet Faucet + + form.pure-form.pure-form-aligned#myForm(action="/faucet_subnet" method="get") + fieldset + //- center + //- h4#source Source Wallet: + .pure-control-group + label() Destination Address + input.pure-input-1-3#text-dest(type='text' placeholder='0x1111111111111111111111111111111111111111' name="text-dest") + .pure-control-group + label() Amount + input#text-amount(type='number' placeholder='10000000' value='10000000' name="text-amount") + + center + .pure-controls + button.pure-button.pure-button-primary(type='button' onclick="submitGet()") Submit + + #final + #result + #error + +script. + function genAddress(){ + fetch('/address') + .then(response => response.json()) + .then(data => { + document.getElementById("address-gen-pub").innerHTML="Address: "+data["publicKey"] + document.getElementById("address-gen-pk").innerHTML="Private Key: "+data["privateKey"] + }) + .catch(error => { + console.error('Error:', error); + document.getElementById("address-gen-pub").innerHTML="Error Generating Address" + document.getElementById("address-gen-pk").innerHTML="Error Generating Address" + }); + } + + function submitGet(){ + var dest = document.getElementById("text-dest").value + var amount = document.getElementById("text-amount").value + var params = new URLSearchParams({dest: dest, amount: amount}) + errorDiv = document.getElementById("error") + resultDiv = document.getElementById("result") + resultDiv.innerHTML = "Submitting TX" + errorDiv.innerHTML = "" + fetch(`/faucet_subnet?${params}`) + .then(response => response.json()).then(data => { + if (data.success){ + display = + ` + Success +
+
To Wallet: ${data.destAddress} +
Balance: ${data.destBalance} +
TX Hash: ${data.txHash} + ` + resultDiv.innerHTML=display + errorDiv.innerHTML="" + + } else { + resultDiv.innerHTML = "" + errorDiv.innerHTML="Error: "+data.message + } + }) + } \ No newline at end of file diff --git a/container-manager/src/views/faucet/pure-min.css b/container-manager/src/views/faucet/pure-min.css new file mode 100644 index 0000000..acdc431 --- /dev/null +++ b/container-manager/src/views/faucet/pure-min.css @@ -0,0 +1,11 @@ +/*! +Pure v3.0.0 +Copyright 2013 Yahoo! +Licensed under the BSD License. +https://github.com/pure-css/pure/blob/master/LICENSE +*/ +/*! +normalize.css v | MIT License | https://necolas.github.io/normalize.css/ +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{display:flex;flex-flow:row wrap;align-content:flex-start}.pure-u{display:inline-block;vertical-align:top}.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;user-select:none;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-0.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:rgba(0,0,0,.8);border:none transparent;background-color:#e6e6e6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;opacity:.4;cursor:not-allowed;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{margin:0;border-radius:0;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input:not([type]):focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0 0}.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;padding:.5em 0}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent;cursor:default}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file diff --git a/container-manager/src/views/generator/about.pug b/container-manager/src/views/generator/about.pug new file mode 100644 index 0000000..caa0b32 --- /dev/null +++ b/container-manager/src/views/generator/about.pug @@ -0,0 +1,5 @@ +html + head + title= title + body + h1= message \ No newline at end of file diff --git a/container-manager/src/views/generator/custom.css b/container-manager/src/views/generator/custom.css new file mode 100644 index 0000000..2410391 --- /dev/null +++ b/container-manager/src/views/generator/custom.css @@ -0,0 +1,155 @@ +#myForm { + margin-left: 15%; + /* margin-right: 200px; */ + max-width: 1100px; +} + +#helper { + margin-left: 15%; + margin-top: 50px; + max-width: 1100px; +} +#helper-title { + margin-right: 10%; +} +.helper-block { + padding: 10px; +} + +.content { + display: none; + background-color: #f0f0f0; + margin-right: 100px; + padding-top: 10px; + padding-bottom: 10px; +} + +#toggle:checked ~ .content { + display: block; +} + +#toggle2:checked ~ .grayedout { + background-color: gray disabled; +} + +#lite-mode-extra-info { + color: red; +} + +#incomplete-required-warning { + color: red; +} + +/* hover trick */ +.info-container { + position: relative; + display: inline-block; +} + +.info-icon { + display: inline-block; + width: 20px; + height: 20px; + background-color: #007bff; + color: white; + border-radius: 50%; + text-align: center; + line-height: 20px; + margin-left: 5px; + cursor: pointer; +} +.info-icon-all-gray { + color: #f0f0f0; + background-color: #f0f0f0; +} + +.tooltip { + visibility: hidden; + width: 200px; + background-color: #555; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px; + position: absolute; + z-index: 1; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + margin-bottom: 10px; + opacity: 0; + transition: opacity 0.3s; +} + +.tooltip::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #555 transparent transparent transparent; +} + +.info-icon:hover + .tooltip, +.info-icon:focus + .tooltip { + visibility: visible; + opacity: 1; +} + +#information1 { + display: none; +} +#expand1:hover + #information1 { + display: block; +} + +/* expanding after selection */ +#ipoption { + display: none; +} +#checkbox-num-machine { + display: none; +} +#checkbox-num-machine:checked + #ipoption { + display: block; +} + +#customkeys { + display: none; +} +#customkeys-checkbox:checked + #customkeys { + display: block; +} + +#customversion { + display: none; +} +#customversion-checkbox:checked + #customversion { + display: block; +} + +#xdczero { + display: none; +} +#xdczero-checkbox:checked + #xdczero { + display: block; +} + +/* #rm-radio-full:checked ~ #xdczero-upper{ + display:block; +} +#rm-radio-lite:checked ~ #xdczero-upper{ + display:block; +} */ + +#zerobidi { + display: none; +} +#zm-radio-bi:checked ~ #zerobidi { + display: block; +} +#zm-radio-one:checked ~ #zerobidi { + display: none; +} diff --git a/container-manager/src/views/generator/index.pug b/container-manager/src/views/generator/index.pug new file mode 100644 index 0000000..6bb3258 --- /dev/null +++ b/container-manager/src/views/generator/index.pug @@ -0,0 +1,382 @@ +head + meta(charset='utf-8') + title Subnet Configuration Generator + style + include ./pure-min.css + include ./custom.css + + +body + center + h1 Subnet Configuration Generator + + //- form.pure-form.pure-form-aligned#myForm(onsubmit="return validateForm()") + form.pure-form.pure-form-aligned#myForm(action="/submit" method="post" onsubmit="return validateForm()") + center + h3 Subnet Configuration + fieldset + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip This cannot be changed later. + label() Network Name + input#text-subnet-name(type='text' placeholder='myxdcsubnet' name="text-subnet-name") + span.pure-form-message-inline Required + + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip At least 2/3 of nodes must be online to have consensus and make the blockchain run. + label() Number of Subnet Nodes + input#text-num-subnet(type='number' placeholder='3' name="text-num-subnet") + span.pure-form-message-inline Required + + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip The generated configs will evenly spread the nodes across the machines. + label() Number of Machines + input#text-num-machine(type='number' placeholder='1' name="text-num-machine" oninput="numMachineMoreThanOne()") + + input#checkbox-num-machine(type='checkbox' name='checkbox-num-machine') + + //- .pure-control-group + //- .info-container + //- .info-icon(tabindex="0" role="button" aria-label="more info") ? + //- .tooltip + //- p - WSL is not supported yet + //- p - Mac is only suitable for testing purposes + //- label() Operating System + //- br + //- br + //- label.pure-radio(for='os-radio-mac') + //- input#os-radio-mac(type='radio' name='osradio' value='os-radio-mac' checked) + //- | Mac + //- br + //- br + //- label.pure-radio(for='os-radio-linux') + //- input#os-radio-linux(type='radio' name='osradio' value='os-radio-linux') + //- | Linux + #ipoption.content + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Private IP is used for Subnet nodes communication. + label() Private IP + input#text-private-ip(type='text' placeholder='192.168.1.1' name="text-private-ip") + span.pure-form-message-inline Required + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Public IP is used for accessing Subnet services, eg. Frontend. + label() Public IP + input#text-public-ip(type='text' placeholder='1.1.1.1' name="text-public-ip") + + + + + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Default is random + label.pure-checkbox(for='customkeys-checkbox') Custom Subnet Keys + input#customkeys-checkbox.checkbox-indent(type='checkbox' name="customkeys-cheeckbox") + #customkeys.content + .pure-control-group + label() Grandmaster PK + input#grandmaster-pk.pure-input-2-3(type='text' placeholder='0x1111111111111111111111111111111111111111111111111111111111111111' name="grandmaster-pk") + center + button.pure-button.pure-button-primary#button-custom-subnet-key(type="button" onclick="customSubnetKeyJs()") Custom Subnet Node Keys + #custom-subnet-key-placeholder + + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Default to stable + label.pure-checkbox(for='customversion-checkbox') Custom Version + input#customversion-checkbox.checkbox-indent(type='checkbox' name="customversion-checkbox") + #customversion.content + center + p Changelog: + .pure-control-group + label() Subnet Node Version + input#customversion-subnet.pure-input(type='text' placeholder='latest' name="customversion-subnet") + .pure-control-group + label() Bootnode Version + input#customversion-bootnode.pure-input(type='text' placeholder='latest' name="customversion-bootnode") + .pure-control-group + label() Relayer Version + input#customversion-relayer.pure-input(type='text' placeholder='latest' name="customversion-relayer") + .pure-control-group + label() Backend Stats Server Version + input#customversion-stats.pure-input(type='text' placeholder='latest' name="customversion-stats") + .pure-control-group + label() Frontend Version + input#customversion-frontend.pure-input(type='text' placeholder='latest' name="customversion-frontend") + .pure-control-group + label() CSC Version + input#customversion-csc.pure-input(type='text' placeholder='latest' name="customversion-csc") + .pure-control-group + label() XDC-Zero Version + input#customversion-zero.pure-input(type='text' placeholder='latest' name="customversion-zero") + + + center + h3 Cross-Chain Configuration + fieldset + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Testnet is recommended for testing. + label() Parent Network + br + br + label.pure-radio(for='pn-radio-testnet') + input#pn-radio-testnet(type='radio' name='pnradio' value='pn-radio-testnet' checked) + | XDC-Testnet/Apothem + //- | XDC-Testnet/Apothem      faucet: + br + br + label.pure-radio(for='pn-radio-devnet') + input#pn-radio-devnet(type='radio' name='pnradio' value='pn-radio-devnet') + //- | XDC-Devnet                     faucet: + | XDC-Devnet + br + br + label.pure-radio(for='pn-radio-mainnet') + input#pn-radio-mainnet(type='radio' name='pnradio' value='pn-radio-mainnet') + | XDC-Mainnet + + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip This Parentchain wallet will be used to deploy the Checkpoint Smart Contract(CSC) on the Parentchain and used by the Relayer to checkpoint Subnet block headers at regular intervals. + label() Parentnet Wallet PK + input#parentnet-wallet-pk.pure-input-1-2(type='text' placeholder='0x1111111111111111111111111111111111111111111111111111111111111111' name="parentnet-wallet-pk") + span.pure-form-message-inline Required + + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip + p - Full: All Subnet block headers are checkpointed on the Parentchain. + p - Lite: Lite mode reduces gas cost by checkpointing only Gap block(450) and Epoch block(900) are checkpointed on the Parentchain. + label() Relayer Mode + br + br + label.pure-radio(for='rm-radio-full') + input#rm-radio-full(type='radio' name='rmradio' value='rm-radio-full' onclick="relayerFullClicked(this)" checked) + | Full + br + br + label.pure-radio(for='rm-radio-lite') + input#rm-radio-lite(type='radio' name='rmradio' value='rm-radio-lite' onclick="relayerLiteClicked(this)") + | Lite + br + br + center + #lite-mode-extra-info + #xdczero-upper + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip XDC-Zero is a Cross-Chain communication framework + label.pure-checkbox(for='xdczero-checkbox') XDC-Zero + input#xdczero-checkbox.checkbox-indent(type='checkbox' name="xdczero-checkbox") + #xdczero.content + center + p Extra Info: + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip This Parentchain Zero Wallet is used for pushing cross-chain communication tx + label() Parentnet Zero Wallet PK + input#parentnet-zero-wallet-pk.pure-input-1-2(type='text' placeholder='0x1111111111111111111111111111111111111111111111111111111111111111' name="parentnet-zero-wallet-pk") + span.pure-form-message-inline Required + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip In Bi-Directional mode, Reverse CSC and Reverse XDC-Zero are also deployed + label() XDC-Zero Mode + br + br + label.pure-radio(for='zm-radio-one') + input#zm-radio-one(type='radio' name='zmradio' value='zm-radio-one' checked) + | One-Directional (Subnet -> Parentnet) + br + br + label.pure-radio(for='zm-radio-bi') + input#zm-radio-bi(type='radio' name='zmradio' value='zm-radio-bi') + | Bi-Directional (Subnet <-> Parentnet) + br + br + #zerobidi.content + //- center + //- p Faucet: + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip This Subnet wallet will be used to checkpoint the Parentchain to Subnet. You can transfer Subnet tokens to this wallet after Subnet is launched. + label() Subnet Wallet PK + input#subnet-wallet-pk.pure-input-1-2(type='text' placeholder='0x1111111111111111111111111111111111111111111111111111111111111111' name="subnet-wallet-pk") + span.pure-form-message-inline Required + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip The Subnet Zero Wallet will be used to submit cross-chain tx to the Subnet. You can transfer Subnet tokens to this wallet after Subnet is launched. + label() Subnet Zero Wallet PK + input#subnet-zero-wallet-pk.pure-input-1-2(type='text' placeholder='0x1111111111111111111111111111111111111111111111111111111111111111' name="subnet-zero-wallet-pk") + span.pure-form-message-inline Required + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Subswap is XDC provided default cross-chain application to handle token transfers between Subnet and Parentnet + label.pure-checkbox(for='subswap-checkbox') Subswap + input#subswap-checkbox.checkbox-indent(type='checkbox' name="subswap-checkbox") + + + + + center + #incomplete-required-warning + .pure-controls + button.pure-button.pure-button-primary(type='submit') Submit + + center + h3#helper-title Helpers + .helper-block + h4 Address Generator + p#address-gen-pub Address: + p#address-gen-pk Private Key: + #disclaim-wrap + button.pure-button.pure-button-primary(type="button" onclick="genAddress()") Generate Address + h8#disclaimer *Generation is done locally on your machine, fully offline. + .helper-block + h4 XDC Faucet + p + a(href="https://faucet.apothem.network/" target="_blank") Testnet(Apothem) Faucet + p + a(href="https://faucet.blocksscan.io/ " target="_blank") Testnet and Devnet Faucet by BlocksScan + .helper-block + h4 Documentation + p + a(href="https://xinfinorg.github.io/xdc-subnet-docs/deployment/launch_subnet" target="_blank") Official Subnet Documentation + p + a(href="https://github.com/XinFinOrg/XDC-Subnet" target="_blank") XDC-Subnet Github + + +script. + function customSubnetKeyJs(){ + const parent = document.getElementById("custom-subnet-key-placeholder") + const count = document.getElementById("text-num-subnet").value + if (!count) { + parent.innerHTML = ''; + const cen = document.createElement("center") + const para = document.createElement("p") + para.innerHTML = "Please first input 'Number of Subnet Nodes'" + cen.appendChild(para) + parent.appendChild(cen) + } else { + parent.innerHTML = ''; + for (i=1;i<=count;i++){ + var d = document.createElement("div") + d.setAttribute("class", "pure-control-group") + const label = document.createElement("label") + label.innerHTML = "Subnet Node " + i + " PK" + const input = document.createElement("input") + const id = "subnet-key"+i + input.setAttribute("id", id) + input.setAttribute("name", id) + input.setAttribute("class", "pure-input-2-3") + input.setAttribute("type", "text") + input.setAttribute("placeholder", "0x1111111111111111111111111111111111111111111111111111111111111111") + + d.appendChild(label) + d.appendChild(input) + parent.appendChild(d) + } + } + } + function numMachineMoreThanOne(){ + const num = document.getElementById("text-num-machine") + const hiddenbox = document.getElementById("checkbox-num-machine") + if (num.value > 1){ + hiddenbox.checked = true; + } else { + hiddenbox.checked = false; + const text1 = document.getElementById("text-private-ip") + const text2 = document.getElementById("text-public-ip") + text1.value = "" + text2.value = "" + } + } + + function relayerLiteClicked(radio){ + const checkbox = document.getElementById("xdczero-checkbox") + checkbox.checked = false; + checkbox.disabled = true; + //- document.getElementById("lite-mode-extra-info").innerHTML="Cannot use XDC-Zero with Lite Relayer" + } + + function relayerFullClicked(radio){ + const checkbox = document.getElementById("xdczero-checkbox") + checkbox.disabled = false; + } + + function validateForm(){ + console.log('validateForm called') + const form = document.forms["myForm"] + + const name = form["text-subnet-name"].value; + const num_subnet = form["text-num-subnet"].value; + const pn_pk = form["parentnet-wallet-pk"].value; + if (name == "" || num_subnet == "" || pn_pk == ""){ + document.getElementById("incomplete-required-warning").innerHTML="Please fill in all required fields" + return false + } + + const num_machines = form["text-num-machine"].value; + if (num_machines > 1){ + const private_ip = form["text-private-ip"].value; + if (private_ip == ""){ + document.getElementById("incomplete-required-warning").innerHTML="Please fill in all required fields" + return false + } + } + const zero = form["xdczero-checkbox"].checked; + if (zero){ + const zero_pk = form["parentnet-zero-wallet-pk"].value; + if (zero_pk == ""){ + document.getElementById("incomplete-required-warning").innerHTML="Please fill in all required fields" + return false + } + + const bidi = form["zm-radio-bi"].checked + if (bidi){ + const subnet_pk = form["subnet-wallet-pk"].value; + const subnet_zero_pk = form["subnet-zero-wallet-pk"].value; + if (subnet_pk == "" || subnet_zero_pk == ""){ + document.getElementById("incomplete-required-warning").innerHTML="Please fill in all required fields" + return false + } + } + } + + + document.getElementById("incomplete-required-warning").innerHTML="" + console.log("pass") + return true + } + + function genAddress(){ + fetch('/address') + .then(response => response.json()) + .then(data => { + document.getElementById("address-gen-pub").innerHTML="Address: "+data["publicKey"] + document.getElementById("address-gen-pk").innerHTML="Private Key: "+data["privateKey"] + }) + .catch(error => { + console.error('Error:', error); + document.getElementById("address-gen-pub").innerHTML="Error Generating Address" + document.getElementById("address-gen-pk").innerHTML="Error Generating Address" + }); + } \ No newline at end of file diff --git a/container-manager/src/views/generator/pure-min.css b/container-manager/src/views/generator/pure-min.css new file mode 100644 index 0000000..acdc431 --- /dev/null +++ b/container-manager/src/views/generator/pure-min.css @@ -0,0 +1,11 @@ +/*! +Pure v3.0.0 +Copyright 2013 Yahoo! +Licensed under the BSD License. +https://github.com/pure-css/pure/blob/master/LICENSE +*/ +/*! +normalize.css v | MIT License | https://necolas.github.io/normalize.css/ +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{display:flex;flex-flow:row wrap;align-content:flex-start}.pure-u{display:inline-block;vertical-align:top}.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;user-select:none;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-0.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:rgba(0,0,0,.8);border:none transparent;background-color:#e6e6e6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;opacity:.4;cursor:not-allowed;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{margin:0;border-radius:0;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input:not([type]):focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0 0}.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;padding:.5em 0}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent;cursor:default}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file diff --git a/container-manager/src/views/generator/submit.pug b/container-manager/src/views/generator/submit.pug new file mode 100644 index 0000000..b018258 --- /dev/null +++ b/container-manager/src/views/generator/submit.pug @@ -0,0 +1,6 @@ +html + head + title="Submitted" + body + h1=message + h1=error \ No newline at end of file diff --git a/container-manager/src/views/index.html b/container-manager/src/views/index.html new file mode 100644 index 0000000..47a6fe2 --- /dev/null +++ b/container-manager/src/views/index.html @@ -0,0 +1,128 @@ + + + + + + Subnet Deployment Wizard + + + +
+
+

Subnet Deployment Wizard

+
+
+
1. Select Subnet Configuration
+
+ + + +
+
+ + + +
+
+ +
+
+
+
+
2. Start Subnet
+ + +   
Confirm mining status: Not Mining
+
+
+
+
3. Add Mainnet/Testnet balance
+
+
+
+
+
+
 Testnet Faucets:
+ + +
+
+
+ +
+
+
4. Deploy Smart Contracts
+ + + + +
+
+
5. Start Required Services
+ + +
+
+
+
6. Start Optional Services
+
+ + +
+
+ + + + +
+
+
+
+
+
+ +
+

Status

+
Status: Loading...
+
+
+ + + + + + + + \ No newline at end of file diff --git a/container-manager/start.sh b/container-manager/start.sh new file mode 100644 index 0000000..f3091ce --- /dev/null +++ b/container-manager/start.sh @@ -0,0 +1,29 @@ +#!/bin/bash +version="v2.0.0" +current_dir="$(cd "$(dirname "$0")" && pwd)" +network_name="docker_net" + +docker pull xinfinorg/subnet-generator:$version +mkdir -p generated/scripts + +if ! docker network inspect "$network_name" > /dev/null 2>&1; then + echo "Network '$network_name' does not exist. Creating it..." + docker network create --subnet 192.168.25.0/24 "$network_name" +else + echo "Joining existing network '$network_name'" +fi + +docker run -d \ + --network "docker_net" --ip=192.168.25.111 \ + -p 5210:5210 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $current_dir/generated:/mount/generated \ + -e HOSTPWD=$current_dir/generated \ + xinfinorg/subnet-generator:$version \ + && \ +echo '' && \ +echo '' && \ +echo '' && \ +echo 'if this is running on your server, first use ssh tunnel: ssh -N -L localhost:5210:localhost:5210 @ -i ' && \ +echo 'if you are using VSCode Remote Explorer, ssh tunnel will be available by default' && \ +echo 'http://localhost:5210 to access Subnet Deployment Wizard' diff --git a/deployment-generator/scripts/docker-down.sh b/deployment-generator/scripts/docker-down.sh index 211e555..e7d6c2b 100755 --- a/deployment-generator/scripts/docker-down.sh +++ b/deployment-generator/scripts/docker-down.sh @@ -12,5 +12,5 @@ if [[ -z $1 ]]; then exit fi -docker compose --env-file ../docker-compose.env --profile $1 down +docker compose --profile $1 down diff --git a/deployment-generator/scripts/docker-up.sh b/deployment-generator/scripts/docker-up.sh index af38c37..f0f3801 100755 --- a/deployment-generator/scripts/docker-up.sh +++ b/deployment-generator/scripts/docker-up.sh @@ -11,6 +11,6 @@ if [[ -z $1 ]]; then exit fi -docker compose --env-file ../docker-compose.env --profile $1 pull -docker compose --env-file ../docker-compose.env --profile $1 up -d +docker compose --env-file --profile $1 pull +docker compose --env-file --profile $1 up -d diff --git a/deployment-generator/scripts/generate.sh b/deployment-generator/scripts/generate.sh index 6d9395b..2214f11 100755 --- a/deployment-generator/scripts/generate.sh +++ b/deployment-generator/scripts/generate.sh @@ -1,5 +1,5 @@ #!/bin/bash -VERSION_GENERATOR="v1.0.1" +VERSION_GENERATOR="v1.1.0" VERSION_GENESIS="v0.3.1" current_dir="$(cd "$(dirname "$0")" && pwd)" @@ -11,7 +11,7 @@ docker pull xinfinorg/xdcsubnets:$VERSION_GENESIS echo '' echo 'go to http://localhost:5210 to access Subnet Configuration Generator UI' -echo 'or use ssh tunnel if this is running on your server' +echo 'or use ssh tunnel if this is running on your server (if using VSCode to SSH, the Remote Explorer will automatically deploy this tunnel)' echo 'ssh -N -L localhost:5210:localhost:5210 @ -i ' mkdir -p generated/scripts docker run -p 5210:5210 -v $current_dir/generated:/app/generated xinfinorg/subnet-generator:$VERSION_GENERATOR diff --git a/deployment-generator/scripts/test.sh b/deployment-generator/scripts/test.sh deleted file mode 100644 index dd180be..0000000 --- a/deployment-generator/scripts/test.sh +++ /dev/null @@ -1,3 +0,0 @@ -asdf - -asdfasdf \ No newline at end of file diff --git a/deployment-generator/src/config_gen.js b/deployment-generator/src/config_gen.js index e923501..581191a 100644 --- a/deployment-generator/src/config_gen.js +++ b/deployment-generator/src/config_gen.js @@ -20,15 +20,19 @@ const config = { relayer_mode: process.env.RELAYER_MODE || "full", docker_image_name: process.env.IMAGE_NAME || "xinfinorg/subnet-generator:latest", - operating_system: process.env.OS || "linux", version: { subnet: process.env.VERSION_SUBNET || "v0.3.1", bootnode: process.env.VERSION_BOOTNODE || "v0.3.1", relayer: process.env.VERSION_RELAYER || "v0.3.1", stats: process.env.VERSION_STATS || "v0.1.11", frontend: process.env.VERSION_FRONTEND || "v0.1.12", - csc: process.env.VERSION_CSC || "v0.2.1", - zero: process.env.VERSION_ZERO || "v0.2.1", + // csc: process.env.VERSION_CSC || "v0.3.0", + csc: process.env.VERSION_CSC || "feature-v0.3.0", + // zero: process.env.VERSION_ZERO || "v0.3.0", + zero: process.env.VERSION_ZERO || "feature-v0.3.0", + subswap_frontend: + process.env.VERSION_SUBSWAP_FRONTEND || "feature-dockerize", + explorer: process.env.VERSION_EXPLORER || "v0.1.0", }, parentnet: { network: process.env.PARENTNET || "testnet", @@ -47,8 +51,7 @@ const config = { subnet_wallet_pk: process.env.SUBNET_WALLET_PK || "", subnet_zero_wallet_pk: process.env.SUBNET_ZERO_WALLET_PK || "", parentnet_zero_wallet_pk: process.env.PARENTNET_ZERO_WALLET_PK || "", - subswap: process.env.SUBSWAP || "" - + subswap: process.env.SUBSWAP || "", }, generator: { output_path: `${__dirname}/../generated/`, @@ -73,8 +76,8 @@ function configSanityCheck(config) { process.exit(1); } - if (config.num_machines === 0 || config.num_subnet === 0) { - console.log("NUM_MACHINE and NUM_SUBNET cannot be 0"); + if (config.num_machines < 1 || config.num_subnet < 1) { + console.log("NUM_MACHINE and NUM_SUBNET must be 1 or more"); process.exit(1); } @@ -103,12 +106,7 @@ function configSanityCheck(config) { process.exit(1); } - if ( - !( - config.relayer_mode === "full" || - config.relayer_mode === "lite" - ) - ) { + if (!(config.relayer_mode === "full" || config.relayer_mode === "lite")) { console.log("RELAYER_MODE only accepts 'full' or 'lite' (default full)"); process.exit(1); } @@ -116,12 +114,12 @@ function configSanityCheck(config) { if ( config.parentnet.network === "devnet" || config.parentnet.network === "testnet" || - config.parentnet.network === "mainnet" + config.parentnet.network === "mainnet" ) { let official_urls = { devnet: "https://devnetstats.apothem.network/devnet", testnet: "https://erpc.apothem.network/", - mainnet: "https://rpc.xdc.org" + mainnet: "https://rpc.xdc.org", }; config.parentnet.url = official_urls[config.parentnet.network]; } else { @@ -197,16 +195,7 @@ function configSanityCheck(config) { config.keys.subnets_addr = output_wallet; } - if (config.operating_system === "mac") { - if (config.num_machines !== 1) { - console.log( - "OS=mac requires NUM_MACHINE=1. Due to Docker network limitation, Subnets on MacOS can only communicate within its own machine. This option is intended for single machine testing environment only" - ); - process.exit(); - } - } - - if (config.zero.zero_mode == "one-directional"){ + if (config.zero.zero_mode == "one-directional") { try { validatePK(config.zero.parentnet_zero_wallet_pk); } catch { @@ -215,17 +204,18 @@ function configSanityCheck(config) { } } - if (config.zero.zero_mode === "bi-directional"){ + if (config.zero.zero_mode === "bi-directional") { try { validatePK(config.zero.subnet_wallet_pk); validatePK(config.zero.subnet_zero_wallet_pk); validatePK(config.zero.parentnet_zero_wallet_pk); } catch { - console.log("Invalid SUBNET_WALLET_PK or SUBNET_ZERO_WALLET_PK or PARENTNET_ZERO_WALLET_PK"); + console.log( + "Invalid SUBNET_WALLET_PK or SUBNET_ZERO_WALLET_PK or PARENTNET_ZERO_WALLET_PK" + ); process.exit(1); } } - return true; } diff --git a/deployment-generator/src/faucet.js b/deployment-generator/src/faucet.js index 72ca0de..3113b14 100755 --- a/deployment-generator/src/faucet.js +++ b/deployment-generator/src/faucet.js @@ -22,10 +22,10 @@ const inputs = process.argv; if (inputs.length == 5) { try { - processTransfer(inputs).then(output => { + processTransfer(inputs).then((output) => { // console.log(output) - process.exit() - }) + process.exit(); + }); } catch (error) { console.log(error); console.log(usage); @@ -50,10 +50,10 @@ async function processTransfer(inputs) { value: ethers.parseEther(amount), }; - try{ + try { await provider._detectNetwork(); - } catch (error){ - throw Error("Cannot connect to RPC") + } catch (error) { + throw Error("Cannot connect to RPC"); } let sendPromise = fromWallet.sendTransaction(tx); @@ -84,8 +84,8 @@ async function processTransfer(inputs) { return { fromBalance: fromBalance, toBalance: toBalance, - txHash: txHash - } + txHash: txHash, + }; } } } @@ -100,13 +100,15 @@ async function faucetServer(inputs) { fromPK = inputs[3]; fromWallet = new ethers.Wallet(fromPK); - if (command != "server") {throw Error("Invalid command")}; + if (command != "server") { + throw Error("Invalid command"); + } } catch (error) { console.log(error); console.log( "Usage: SUBNET_URL= node faucet.js server " ); - process.exit() + process.exit(); } app.set("view engine", "pug"); app.set("views", path.join(__dirname, "views_faucet")); @@ -135,31 +137,34 @@ async function faucetServer(inputs) { }); }); - router.get("/faucet", async function(req, res) { + router.get("/faucet", async function (req, res) { console.log(req.query); try { - toAddress = req.query.dest - amount = req.query.amount - if (!ethers.isAddress(toAddress)) throw Error("Invalid destination address") - if (isNaN(Number(amount)) || parseFloat(amount) <= 0 || amount == "") throw Error("Invalid Amount") - if (parseFloat(amount) > 1000000) throw Error("Faucet request over 1,000,000 is not allowed") + toAddress = req.query.dest; + amount = req.query.amount; + if (!ethers.isAddress(toAddress)) + throw Error("Invalid destination address"); + if (isNaN(Number(amount)) || parseFloat(amount) <= 0 || amount == "") + throw Error("Invalid Amount"); + if (parseFloat(amount) > 1000000) + throw Error("Faucet request over 1,000,000 is not allowed"); let inputs = ["", "", fromPK, toAddress, amount]; - const {fromBalance, toBalance, txHash} = await processTransfer(inputs); + const { fromBalance, toBalance, txHash } = await processTransfer(inputs); res.json({ success: true, sourceAddress: fromWallet.address, destAddress: toAddress, sourceBalance: fromBalance, destBalance: toBalance, - txHash: txHash - }) + txHash: txHash, + }); } catch (error) { console.log(error); - console.log(error.message) + console.log(error.message); res.json({ success: false, - message: error.message - }) + message: error.message, + }); } }); diff --git a/deployment-generator/src/gen.env b/deployment-generator/src/gen.env index 4d1f5ec..2d078ac 100644 --- a/deployment-generator/src/gen.env +++ b/deployment-generator/src/gen.env @@ -1,18 +1,11 @@ -NETWORK_NAME=4 +NETWORK_NAME=gg NUM_SUBNET=3 -OS=mac PARENTNET=testnet -PARENTNET_WALLET_PK=0x1111111111111111111111111111111111111111111111111111111111111111 +PARENTNET_WALLET_PK=0x4da0711c4780696f725dc6ad44751b6a9e15a80a0bcb41eca6bf4510de64172f RELAYER_MODE=full MAIN_IP=127.0.0.1 NUM_MACHINE=1 - -XDC_ZERO=one-directional -PARENTNET_ZERO_WALLET_PK=jk - - -SUBNET_URL=https://erpc.apothem.network/ \ No newline at end of file diff --git a/deployment-generator/src/gen.js b/deployment-generator/src/gen.js index dd49c49..c43ae75 100644 --- a/deployment-generator/src/gen.js +++ b/deployment-generator/src/gen.js @@ -1,11 +1,11 @@ const fs = require("fs"); const yaml = require("js-yaml"); const { exit } = require("process"); -const config = require("./config_gen"); const gen_compose = require("./gen_compose"); const gen_env = require("./gen_env"); const gen_other = require("./gen_other"); - +const configModule = require("./config_gen"); +const config = configModule.config; Object.freeze(config); // console.log(config) @@ -32,7 +32,7 @@ num_per_machine.reverse(); // let first machines host services, put fewer subnet // gen docker-compose let doc = { - version: "3.7", + // version: "3.7", //docker deprecated attribute services: {}, }; @@ -56,31 +56,29 @@ Object.entries(subnet_services).forEach((entry) => { const [key, value] = entry; doc["services"][key] = value; }); -// checkpoint smartcontract deployment config -let deployment_json = gen_other.genDeploymentJson(keys); -if (config.operating_system === "mac") { - doc, (ip_record = gen_compose.injectMacConfig(doc)); - commonconf = gen_env.genServicesConfigMac(ip_record); - subnetconf = []; - for (let i = 1; i <= config.num_subnet; i++) { - subnetconf.push(gen_env.genSubnetConfigMac(i, keys, ip_record)); - } - deployconf = gen_env.genContractDeployEnvMac(); -} else if (config.operating_system === "linux") { - commonconf = gen_env.genServicesConfig(); - subnetconf = []; - for (let i = 1; i <= config.num_subnet; i++) { - subnetconf.push(gen_env.genSubnetConfig(i, keys)); - } - deployconf = gen_env.genContractDeployEnv(); -} else { - console.log(`ERROR: unknown OS ${config.operating_system} not supported`); - process.exit(1); +const subswap_compose = gen_compose.genSubswapFrontend(); +Object.entries(subswap_compose).forEach((entry) => { + const [key, value] = entry; + doc["services"][key] = value; +}); + +const explorer_compose = gen_compose.genExplorer(); +Object.entries(explorer_compose).forEach((entry) => { + const [key, value] = entry; + doc["services"][key] = value; +}); + +// checkpoint smartcontract deployment config +doc, (ip_record = gen_compose.injectNetworkConfig(doc)); +const commonconf = gen_env.genServicesConfig(ip_record); +subnetconf = []; +for (let i = 1; i <= config.num_subnet; i++) { + subnetconf.push(gen_env.genSubnetConfig(i, keys, ip_record)); } +const deployconf = gen_env.genContractDeployEnv(ip_record); const compose_content = yaml.dump(doc, {}); -const compose_conf = gen_compose.genComposeEnv(); // deployment commands list const commands = gen_other.genCommands(); @@ -154,25 +152,6 @@ function writeGenerated(output_dir) { ); } - fs.writeFileSync(`${output_dir}/docker-compose.env`, compose_conf, (err) => { - if (err) { - console.error(err); - exit(); - } - }); - - deployment_json = JSON.stringify(deployment_json, null, 2); - fs.writeFileSync( - `${output_dir}/deployment.config.json`, - deployment_json, - (err) => { - if (err) { - console.error("Error writing file:", err); - exit(); - } - } - ); - fs.writeFileSync( `${output_dir}/genesis_input.yml`, genesis_input_file, diff --git a/deployment-generator/src/gen_compose.js b/deployment-generator/src/gen_compose.js index 6407e05..b2ab4fd 100644 --- a/deployment-generator/src/gen_compose.js +++ b/deployment-generator/src/gen_compose.js @@ -1,40 +1,41 @@ const net = require("net"); -const config = require("./config_gen"); +const configModule = require("./config_gen"); +const config = configModule.config; Object.freeze(config); module.exports = { genSubnetNodes, genBootNode, - genObserver, genServices, - genComposeEnv, - injectMacConfig, + injectNetworkConfig, + genSubswapFrontend, + genExplorer, }; function genSubnetNodes(machine_id, num, start_num = 1) { let subnet_nodes = {}; for (let i = start_num; i < start_num + num; i++) { const node_name = "subnet" + i.toString(); - const volume = "./xdcchain" + i.toString() + ":/work/xdcchain"; - const config_path = "${SUBNET_CONFIG_PATH}/subnet" + i.toString() + ".env"; + const volume = "${PWD}/xdcchain" + i.toString() + ":/work/xdcchain"; + const config_path = "subnet" + i.toString() + ".env"; const compose_profile = "machine" + machine_id.toString(); const port = 20302 + i; const rpcport = 8544 + i; const wsport = 9554 + i; subnet_nodes[node_name] = { image: `xinfinorg/xdcsubnets:${config.version.subnet}`, - volumes: [ - volume, - "${SUBNET_CONFIG_PATH}/genesis.json:/work/genesis.json", - ], + volumes: [volume, "${PWD}/genesis.json:/work/genesis.json"], restart: "always", network_mode: "host", env_file: [config_path], profiles: [compose_profile], ports: [ - `${port}:${port}`, - `${rpcport}:${rpcport}`, - `${wsport}:${wsport}`, + `${port}:${port}/tcp`, + `${port}:${port}/udp`, + `${rpcport}:${rpcport}/tcp`, + `${rpcport}:${rpcport}/udp`, + `${wsport}:${wsport}/tcp`, + `${wsport}:${wsport}/udp`, ], }; } @@ -42,13 +43,13 @@ function genSubnetNodes(machine_id, num, start_num = 1) { } function genBootNode(machine_id) { - let config_path = "${SUBNET_CONFIG_PATH}/common.env"; + let config_path = "common.env"; const machine = "machine" + machine_id.toString(); const bootnode = { image: `xinfinorg/xdcsubnets:${config.version.bootnode}`, restart: "always", env_file: config_path, - volumes: ["./bootnodes:/work/bootnodes"], + volumes: ["${PWD}/bootnodes:/work/bootnodes"], entrypoint: ["bash", "/work/start-bootnode.sh"], command: ["-verbosity", "6", "-nodekey", "bootnode.key"], ports: ["20301:20301/tcp", "20301:20301/udp"], @@ -57,27 +58,15 @@ function genBootNode(machine_id) { return bootnode; } -function genObserver(machine_id) { - const config_path = "${SUBNET_CONFIG_PATH}/common.env"; - const machine = "machine" + machine_id.toString(); - const observer = { - image: `xinfinorg/devnet:${config.version.observer}`, - restart: "always", - env_file: config_path, - ports: ["20302:30303", "7545:8545", "7555:8555"], - profiles: [machine], - }; - return observer; -} - function genServices(machine_id) { - const config_path = "${SUBNET_CONFIG_PATH}/common.env"; + const config_path = "common.env"; const machine = "services"; + const volume_path = "${PWD}" + "/" + config_path; const frontend = { image: `xinfinorg/subnet-frontend:${config.version.frontend}`, restart: "always", env_file: config_path, // not used directly (injected via volume) but required to trigger restart if common.env changes - volumes: [`${config_path}:/app/.env.local`], + volumes: [`${volume_path}:/app/.env.local`], ports: ["5214:5214"], profiles: [machine], }; @@ -92,7 +81,7 @@ function genServices(machine_id) { image: `xinfinorg/subnet-stats-service:${config.version.stats}`, restart: "always", env_file: config_path, - volumes: ["./stats-service/logs:/app/logs"], + volumes: ["${PWD}/stats-service/logs:/app/logs"], ports: ["5213:5213"], profiles: [machine], }; @@ -110,13 +99,36 @@ function genServices(machine_id) { return services; } -function genComposeEnv() { - // conf_path = `SUBNET_CONFIG_PATH=${config.deployment_path}/generated/`; - conf_path = `SUBNET_CONFIG_PATH=$PWD` - return conf_path; +function genSubswapFrontend() { + const subswap_frontend = { + image: `xinfinorg/subswap-frontend:${config.version.subswap_frontend}`, + restart: "always", + volumes: [ + "${PWD}/subswap-frontend.config.json:/app/subswap-frontend.config.json", + ], + ports: ["5216:5216"], + profiles: ["subswap"], + }; + const subswap = { + subswap_frontend, + }; + return subswap; +} + +function genExplorer() { + const explorer_db = {}; + const explorer_ui = {}; + const explorer_indexer = {}; + + const explorer = { + // db: explorer_db, + // ui: explorer_ui, + // idx: explorer_indexer, + }; + return explorer; } -function injectMacConfig(compose_object) { +function injectNetworkConfig(compose_object) { // networks: // docker_net: // driver: bridge @@ -125,6 +137,7 @@ function injectMacConfig(compose_object) { // - subnet: 192.168.25.0/24 const network = { docker_net: { + external: true, driver: "bridge", ipam: { config: [{ subnet: "192.168.25.0/24" }], @@ -139,15 +152,20 @@ function injectMacConfig(compose_object) { let record_services_ip = {}; const ip_string_base = "192.168.25."; - let start_ip = 11; + let start_ip_subnet = 11; + let start_ip_service = 51; Object.entries(compose_object["services"]).forEach((entry) => { const [key, value] = entry; - const component_ip = ip_string_base + parseInt(start_ip); - start_ip += 1; + let component_ip; + if (key.startsWith("subnet")) { + component_ip = ip_string_base + parseInt(start_ip_subnet); + start_ip_subnet += 1; + } else { + component_ip = ip_string_base + parseInt(start_ip_service); + start_ip_service += 1; + } if (!net.isIP(component_ip)) { - console.log( - `ERROR: found invalid IP assignment ${component_ip} in mac mode` - ); + console.log(`ERROR: found invalid IP assignment ${component_ip}`); process.exit(1); } const component_network = { diff --git a/deployment-generator/src/gen_env.js b/deployment-generator/src/gen_env.js index ec81f8b..2dd9c52 100644 --- a/deployment-generator/src/gen_env.js +++ b/deployment-generator/src/gen_env.js @@ -1,59 +1,33 @@ -const config = require("./config_gen"); +const configModule = require("./config_gen"); +const config = configModule.config; Object.freeze(config); module.exports = { genSubnetConfig, - genSubnetConfigMac, genServicesConfig, - genServicesConfigMac, genContractDeployEnv, - genContractDeployEnvMac, }; -function genSubnetConfig(subnet_id, key) { +function genSubnetConfig(subnet_id, key, ip_record) { const key_name = `key${subnet_id}`; let private_key = key[key_name]["PrivateKey"]; private_key = private_key.slice(2, private_key.length); // remove 0x for subnet conf const port = 20303 + subnet_id - 1; const rpcport = 8545 + subnet_id - 1; const wsport = 9555 + subnet_id - 1; + const bootnode_ip = + config.num_machines === 1 ? ip_record["bootnode"] : config.ip_1; + const stats_ip = config.num_machines === 1 ? ip_record["stats"] : config.ip_1; const config_env = ` INSTANCE_NAME=subnet${subnet_id} PRIVATE_KEY=${private_key} BOOTNODES=enode://cc566d1033f21c7eb0eb9f403bb651f3949b5f63b40683917\ 765c343f9c0c596e9cd021e2e8416908cbc3ab7d6f6671a83c85f7b121c1872f8be\ -50a591723a5d@${config.ip_1}:20301 +50a591723a5d@${bootnode_ip}:20301 NETWORK_ID=${config.network_id} SYNC_MODE=full RPC_API=db,eth,debug,miner,net,shh,txpool,personal,web3,XDPoS -STATS_SERVICE_ADDRESS=${config.ip_1}:5213 -STATS_SECRET=${config.secret_string} -PORT=${port} -RPCPORT=${rpcport} -WSPORT=${wsport} -LOG_LEVEL=2 -`; - - return config_env; -} - -function genSubnetConfigMac(subnet_id, key, ip_record) { - const key_name = `key${subnet_id}`; - let private_key = key[key_name]["PrivateKey"]; - private_key = private_key.slice(2, private_key.length); // remove 0x for subnet conf - const port = 20303 + subnet_id - 1; - const rpcport = 8545 + subnet_id - 1; - const wsport = 9555 + subnet_id - 1; - const config_env = ` -INSTANCE_NAME=subnet${subnet_id} -PRIVATE_KEY=${private_key} -BOOTNODES=enode://cc566d1033f21c7eb0eb9f403bb651f3949b5f63b40683917\ -765c343f9c0c596e9cd021e2e8416908cbc3ab7d6f6671a83c85f7b121c1872f8be\ -50a591723a5d@${ip_record["bootnode"]}:20301 -NETWORK_ID=${config.network_id} -SYNC_MODE=full -RPC_API=db,eth,debug,miner,net,shh,txpool,personal,web3,XDPoS -STATS_SERVICE_ADDRESS=${ip_record["stats"]}:5213 +STATS_SERVICE_ADDRESS=${stats_ip}:5213 STATS_SECRET=${config.secret_string} PORT=${port} RPCPORT=${rpcport} @@ -66,15 +40,19 @@ LOG_LEVEL=2 function genServicesConfig() { const url = config.parentnet.url; + const bootnode_ip = + config.num_machines === 1 ? ip_record["bootnode"] : config.ip_1; + const subnet_ip = + config.num_machines === 1 ? ip_record["subnet1"] : config.ip_1; let config_env = ` # Bootnode -EXTIP=${config.ip_1} +EXTIP=${bootnode_ip} BOOTNODE_PORT=20301 # Stats and relayer PARENTNET_URL=${url} PARENTNET_WALLET=${config.parentnet.pubkey} -SUBNET_URL=http://${config.ip_1}:8545 +SUBNET_URL=http://${subnet_ip}:8545 RELAYER_MODE=${config.relayer_mode} SLACK_WEBHOOK=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX CORS_ALLOW_ORIGIN=* @@ -87,126 +65,39 @@ VITE_SUBNET_RPC=http://${config.public_ip}:8545 STATS_SECRET=${config.secret_string} # CSC -CHECKPOINT_CONTRACT=0x0000000000000000000000000000000000000000 PARENTNET_WALLET_PK=${config.parentnet.privatekey} `; -if (config.zero.zero_mode == 'one-directional'){ - config_env += ` -# # XDC-ZERO. It's optional. Don't uncomment it if not planning to enable it -# SUBNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} - ` - -} else if (config.zero.zero_mode == 'bi-directional'){ - config_env += ` -# # XDC-ZERO. It's optional. Don't uncomment it if not planning to enable it -# SUBNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} - -# # Reverse-XDC-ZERO. optional. -# REVERSE_CHECKPOINT_CONTRACT=0x0000000000000000000000000000000000000000 -# SUBNET_WALLET_PK=${config.zero.subnet_wallet_pk} -# SUBNET_ZERO_WALLET_PK=${config.zero.subnet_zero_wallet_pk} - ` -} + if (config.zero.zero_mode == "one-directional") { + config_env += ` +# XDC-ZERO +PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} + `; + } else if (config.zero.zero_mode == "bi-directional") { + config_env += ` +# XDC-ZERO +PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} +SUBNET_WALLET_PK=${config.zero.subnet_wallet_pk} +SUBNET_ZERO_WALLET_PK=${config.zero.subnet_zero_wallet_pk} + `; + } // # Parent Chain Observe Node // PARENTNET_NODE_NAME=mainnet_observer // PRIVATE_KEYS=11111111111111111111111111111111111111111111111111111111111111 return config_env; } -function genServicesConfigMac(ip_record) { - const url = config.parentnet.url; - let config_env = ` -# Bootnode -EXTIP=${ip_record["bootnode"]} -BOOTNODE_PORT=20301 - -# Stats and relayer -PARENTNET_URL=${url} -PARENTNET_WALLET=${config.parentnet.pubkey} -SUBNET_URL=http://${ip_record["subnet1"]}:8545 -RELAYER_MODE=${config.relayer_mode} -SLACK_WEBHOOK=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX -CORS_ALLOW_ORIGIN=* - -# Frontend -VITE_SUBNET_URL=http://127.0.0.1:5213 -VITE_SUBNET_RPC=http://127.0.0.1:8545 - -# Share Variable -STATS_SECRET=${config.secret_string} - -# CSC -CHECKPOINT_CONTRACT=0x0000000000000000000000000000000000000000 -PARENTNET_WALLET_PK=${config.parentnet.privatekey} - -`; -if (config.zero.zero_mode == 'one-directional'){ - config_env += ` -# # XDC-ZERO. It's optional. Don't uncomment it if not planning to enable it -# SUBNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} - ` - -} else if (config.zero.zero_mode == 'bi-directional'){ - config_env += ` -# # XDC-ZERO. It's optional. Don't uncomment it if not planning to enable it -# SUBNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_WALLET_PK=${config.zero.parentnet_zero_wallet_pk} - -# # Reverse-XDC-ZERO. optional. -# REVERSE_CHECKPOINT_CONTRACT=0x0000000000000000000000000000000000000000 -# SUBNET_WALLET_PK=${config.zero.subnet_wallet_pk} -# SUBNET_ZERO_WALLET_PK=${config.zero.subnet_zero_wallet_pk} - ` -} - return config_env; -} - -function genContractDeployEnvMac() { - const config_deploy = ` -PARENTNET_URL=${config.parentnet.url} -SUBNET_URL=http://${ip_record["subnet1"]}:8545 - -PARENTNET_PK=${config.parentnet.privatekey} -SUBNET_PK=${config.keys.grandmaster_pk} - -CSC=0x0000000000000000000000000000000000000000 -REVERSE_CSC=0x0000000000000000000000000000000000000000 - -# # For Subswap deployment. It's optional. -# SUBNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# SUBNET_APP=0x0000000000000000000000000000000000000000 -# PARENTNET_APP=0x0000000000000000000000000000000000000000 -`; - return config_deploy; -} - -function genContractDeployEnv() { +function genContractDeployEnv(ip_record) { + const subnet_ip = + config.num_machines === 1 ? ip_record["subnet1"] : config.ip_1; const config_deploy = ` PARENTNET_URL=${config.parentnet.url} -SUBNET_URL=http://${config.ip_1}:8545 - +SUBNET_URL=http://${subnet_ip}:8545 PARENTNET_PK=${config.parentnet.privatekey} SUBNET_PK=${config.keys.grandmaster_pk} -CSC=0x0000000000000000000000000000000000000000 -REVERSE_CSC=0x0000000000000000000000000000000000000000 - -# # For Subswap deployment. It's optional. -# SUBNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# PARENTNET_ZERO_CONTRACT=0x0000000000000000000000000000000000000000 -# SUBNET_APP=0x0000000000000000000000000000000000000000 -# PARENTNET_APP=0x0000000000000000000000000000000000000000 `; return config_deploy; } diff --git a/deployment-generator/src/gen_other.js b/deployment-generator/src/gen_other.js index 937785e..3062844 100644 --- a/deployment-generator/src/gen_other.js +++ b/deployment-generator/src/gen_other.js @@ -1,4 +1,5 @@ -const config = require("./config_gen"); +const configModule = require("./config_gen"); +const config = configModule.config; Object.freeze(config); module.exports = { @@ -70,16 +71,7 @@ function genDeploymentJson(keys) { // } function genCommands() { - const conf_path = __dirname + "/config/"; - const set_env = "SUBNET_CONFIG_PATH=" + conf_path; - let csc_mode = "full"; - if (config.relayer_mode == "lite") { - csc_mode = "lite"; - } - let os_extra = ""; - if (config.operating_system == "mac"){ - os_extra = " --network generated_docker_net " - } + const csc_mode = config.relayer_mode === "lite" ? "lite" : "full"; let commands = ""; commands += "Start under generated/ directory\n"; commands += "\n1. Deploy Subnet nodes\n"; @@ -90,109 +82,164 @@ function genCommands() { machine_name + `: deploy subnet on machine${i}\n`; } if (i !== 1) { - commands += ` Prerequisite: copy docker-compose.yml,docker-compose.env,config/subnetX.env to ${machine_name}. Make sure docker-compose.env points to subnetX.env directory.\n`; + commands += ` Prerequisite: copy docker-compose.yml, genesis.json, config/subnetX.env to ${machine_name}.\n`; } - commands += ` docker compose --env-file docker-compose.env --profile ${machine_name} pull\n`; - commands += ` docker compose --env-file docker-compose.env --profile ${machine_name} up -d\n\n`; + commands += ` docker compose --profile ${machine_name} pull\n`; + commands += ` docker compose --profile ${machine_name} up -d\n\n`; } commands += "\n2. After 60 seconds, confirm the Subnet is running correctly\n"; commands += " ./scripts/check-mining.sh\n"; commands += "\n3. Deploy Checkpoint Smart Contract (CSC)\n"; commands += ` docker pull xinfinorg/csc:${config.version.csc}\n`; - commands += ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/csc:${config.version.csc} ${csc_mode}\n`; - commands += "\n4. Add CSC configuration to common.env\n"; - commands += " - copy step 3. output CHECKPOINT_CONTRACT to common.env\n"; - commands += "\n5. Start services (relayer, backend, frontend)\n"; - commands += ` docker compose --env-file docker-compose.env --profile services pull\n`; - commands += ` docker compose --env-file docker-compose.env --profile services up -d\n`; - commands += "\n6. Confirm Subnet services through browser UI\n"; + commands += + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/csc:${config.version.csc} ${csc_mode}\n`; + commands += "\n4. Start services (relayer, backend, frontend)\n"; + commands += ` docker compose --profile services pull\n`; + commands += ` docker compose --profile services up -d\n`; + commands += "\n5. Confirm Subnet services through browser UI\n"; commands += ` Frontend: http://${config.public_ip}:5214\n`; commands += ` Relayer: http://${config.public_ip}:5215\n`; if (config.zero.zero_mode == "") { } else if (config.zero.zero_mode == "one-directional") { - commands += "\n\nDeploy XDC-Zero crosschain framework (one-directional)\n"; - commands += "\n1. Add CSC configuration to contract_deploy.env\n"; - commands += - " - copy CHECKPOINT_CONTRACT from common.env to and rename it to CSC in contract_deploy.env\n"; - commands += - " - copy CHECKPOINT_CONTRACT from common.env to and rename it to REVERSE_CSC in contract_deploy.env\n"; - commands += "\n2. Deploy XDC-Zero\n"; - commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; - commands += ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; if (config.zero.subswap == "true") { - commands += " \n Deploy Subswap and Register Application to XDC-Zero\n"; - commands += - " - copy SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to contract_deploy.env\n"; + commands += "\n6. Deploy XDC-Zero and Subswap\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; commands += - ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/xdc-zero:v0.1.0 subswap\n`; - commands += - " - copy SUBNET_APP and PARENTNET_APP to contract_deploy.env\n"; + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} zeroandsubswap\n`; + commands += "\n7. Run Subswap Frontend\n"; + commands += ` docker compose --profile subswap pull\n`; + commands += ` docker compose --profile subswap up -d\n`; + commands += "\n8. Confirm Subswap UI\n"; + commands += ` Subswap-Frontend: http://${config.public_ip}:5216\n`; + } else { + commands += "\n6. Deploy XDC-Zero\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; commands += - ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/xdc-zero:v0.1.0 applicationregister\n`; + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; } - commands += "\n3. Add XDC-Zero Configuration to common.env\n"; - commands += - " - copy step 2. output SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to common.env\n"; - commands += - " - Add PARENTNET_ZERO_WALLET_PK to common.env, this should be different from PARENTNET_WALLET_PK\n"; - commands += "\n4. Restart Relayer\n"; - commands += ` docker compose --env-file docker-compose.env --profile services down\n`; - commands += ` docker compose --env-file docker-compose.env --profile services up -d\n`; - commands += "\n5. Confirm Relayer is running \n"; - commands += ` Relayer: http://${config.public_ip}:5215\n`; - commands += ` Frontend: http://${config.public_ip}:5214\n`; - + commands += "\nRestart Relayer\n"; + commands += ` docker compose --profile services down\n`; + commands += ` docker compose --profile services up -d\n`; } else if (config.zero.zero_mode == "bi-directional") { - commands += "\n\nDeploy XDC-Zero crosschain framework (bi-directional)\n"; - commands += "\n1. Deploy Reverse Checkpoint Smart Contract (Reverse CSC)\n"; + commands += "\n6. Deploy Reverse CSC\n"; commands += ` docker pull xinfinorg/csc:${config.version.csc}\n`; - commands += ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/csc:${config.version.csc} reversefull\n`; - commands += "\n2. Add CSC and Reverse CSC configuration\n"; - commands += - " - copy previous step output REVERSE_CHECKPOINT_CONTRACT to common.env\n"; commands += - " - copy REVERSE_CHECKPOINT_CONTRACT from common.env and rename it to REVERSE_CSC in contract_deploy.env\n"; - commands += - " - copy CHECKPOINT_CONTRACT from common.env and rename it to CSC in contract_deploy.env\n"; - commands += "\n3. Deploy XDC-Zero\n"; - commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; - commands += ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; - if (config.zero.subswap) { - commands += - " \n Deploy Subswap and Register Application to XDC-Zero\n"; - commands += - " - copy SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to contract_deploy.env\n"; - commands += - ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/xdc-zero:v0.1.0 subswap\n`; + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/csc:${config.version.csc} reversefull\n`; + if (config.zero.subswap == "true") { + commands += "\n7. Deploy XDC-Zero and Subswap\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; commands += - " - copy SUBNET_APP and PARENTNET_APP to contract_deploy.env\n"; + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} zeroandsubswap\n`; + commands += "\n8. Run Subswap Frontend\n"; + commands += ` docker compose --profile subswap_frontend pull\n`; + commands += ` docker compose --profile subswap_frontend up -d\n`; + commands += "\n9. Confirm Subswap UI\n"; + commands += ` Subswap-Frontend: http://${config.public_ip}:5216\n`; + } else { + commands += "\n7. Deploy XDC-Zero\n"; + commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; commands += - ` docker run --env-file contract_deploy.env ${os_extra} xinfinorg/xdc-zero:v0.1.0 applicationregister\n`; + " docker run -v ${PWD}/:/app/cicd/mount/" + + ` --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; } - commands += "\n4. Add XDC-Zero Configuration to common.env\n"; - commands += - " - copy step 3. output SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to common.env\n"; - commands += - " - Add SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK to common.env, they should be different from each other\n"; - commands += - " - Add PARENTNET_ZERO_WALLET_PK to common.env, this should be different from PARENTNET_WALLET_PK\n"; - commands += "\n5. Transfer Subnet tokens to SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK\n"; - commands += " ./scripts/faucet.sh\n" - commands += " - check keys.json for the Grandmaster Key as the source wallet\n" - commands += " - for the destination wallet you should use the public address of SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK\n" - commands += "\n6. Restart Relayer\n"; - commands += ` docker compose --env-file docker-compose.env --profile services down\n`; - commands += ` docker compose --env-file docker-compose.env --profile services up -d\n`; - commands += "\n7. Confirm Relayer is running \n"; - commands += ` Relayer: http://${config.public_ip}:5215\n`; - commands += ` Frontend: http://${config.public_ip}:5214\n`; + commands += "\nRestart Relayer\n"; + commands += ` docker compose --profile services down\n`; + commands += ` docker compose --profile services up -d\n`; } else { console.log("Error: Invalid XDC-Zero mode"); exit(); } + // if (config.zero.zero_mode == "") { + // } else if (config.zero.zero_mode == "one-directional") { + // commands += "\n\nDeploy XDC-Zero crosschain framework (one-directional)\n"; + // commands += "\n1. Add CSC configuration to contract_deploy.env\n"; + // commands += + // " - copy CHECKPOINT_CONTRACT from common.env to and rename it to CSC in contract_deploy.env\n"; + // commands += + // " - copy CHECKPOINT_CONTRACT from common.env to and rename it to REVERSE_CSC in contract_deploy.env\n"; + // commands += "\n2. Deploy XDC-Zero\n"; + // commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + // commands += ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; + // if (config.zero.subswap == "true") { + // commands += " \n Deploy Subswap and Register Application to XDC-Zero\n"; + // commands += + // " - copy SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 subswap\n`; + // commands += + // " - copy SUBNET_APP and PARENTNET_APP to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 applicationregister\n`; + // } + // commands += "\n3. Add XDC-Zero Configuration to common.env\n"; + // commands += + // " - copy step 2. output SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to common.env\n"; + // commands += + // " - Add PARENTNET_ZERO_WALLET_PK to common.env, this should be different from PARENTNET_WALLET_PK\n"; + // commands += "\n4. Restart Relayer\n"; + // commands += ` docker compose --profile services down\n`; + // commands += ` docker compose --profile services up -d\n`; + // commands += "\n5. Confirm Relayer is running \n"; + // commands += ` Relayer: http://${config.public_ip}:5215\n`; + // commands += ` Frontend: http://${config.public_ip}:5214\n`; + + // } else if (config.zero.zero_mode == "bi-directional") { + // commands += "\n\nDeploy XDC-Zero crosschain framework (bi-directional)\n"; + // commands += "\n1. Deploy Reverse Checkpoint Smart Contract (Reverse CSC)\n"; + // commands += ` docker pull xinfinorg/csc:${config.version.csc}\n`; + // commands += ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/csc:${config.version.csc} reversefull\n`; + // commands += "\n2. Add CSC and Reverse CSC configuration\n"; + // commands += + // " - copy previous step output REVERSE_CHECKPOINT_CONTRACT to common.env\n"; + // commands += + // " - copy REVERSE_CHECKPOINT_CONTRACT from common.env and rename it to REVERSE_CSC in contract_deploy.env\n"; + // commands += + // " - copy CHECKPOINT_CONTRACT from common.env and rename it to CSC in contract_deploy.env\n"; + // commands += "\n3. Deploy XDC-Zero\n"; + // commands += ` docker pull xinfinorg/xdc-zero:${config.version.zero}\n`; + // commands += ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:${config.version.zero} endpointandregisterchain\n`; + // if (config.zero.subswap) { + // commands += + // " \n Deploy Subswap and Register Application to XDC-Zero\n"; + // commands += + // " - copy SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 subswap\n`; + // commands += + // " - copy SUBNET_APP and PARENTNET_APP to contract_deploy.env\n"; + // commands += + // ` docker run --env-file contract_deploy.env --network generated_docker_net xinfinorg/xdc-zero:v0.1.0 applicationregister\n`; + // } + // commands += "\n4. Add XDC-Zero Configuration to common.env\n"; + // commands += + // " - copy step 3. output SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT to common.env\n"; + // commands += + // " - Add SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK to common.env, they should be different from each other\n"; + // commands += + // " - Add PARENTNET_ZERO_WALLET_PK to common.env, this should be different from PARENTNET_WALLET_PK\n"; + // commands += "\n5. Transfer Subnet tokens to SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK\n"; + // commands += " ./scripts/faucet.sh\n" + // commands += " - check keys.json for the Grandmaster Key as the source wallet\n" + // commands += " - for the destination wallet you should use the public address of SUBNET_WALLET_PK and SUBNET_ZERO_WALLET_PK\n" + // commands += "\n6. Restart Relayer\n"; + // commands += ` docker compose --profile services down\n`; + // commands += ` docker compose --profile services up -d\n`; + // commands += "\n7. Confirm Relayer is running \n"; + // commands += ` Relayer: http://${config.public_ip}:5215\n`; + // commands += ` Frontend: http://${config.public_ip}:5214\n`; + // } else { + // console.log("Error: Invalid XDC-Zero mode"); + // exit(); + // } + return commands; } diff --git a/deployment-generator/src/ui.js b/deployment-generator/src/ui.js index 25e50ef..5106773 100644 --- a/deployment-generator/src/ui.js +++ b/deployment-generator/src/ui.js @@ -1,26 +1,27 @@ process.chdir(__dirname); const { execSync } = require("child_process"); const fs = require("fs"); -const ethers = require('ethers'); +const ethers = require("ethers"); const express = require("express"); const app = express(); const path = require("path"); const router = express.Router(); - + app.set("view engine", "pug"); app.set("views", path.join(__dirname, "views")); -router.use(express.urlencoded({ - extended: true -})) -app.use(express.static('css')); +router.use( + express.urlencoded({ + extended: true, + }) +); +app.use(express.static("css")); router.get("/", (req, res) => { - res.render("index", { - }); + res.render("index", {}); }); - + router.post("/submit", (req, res) => { - gen_env = genGenEnv(req.body) + gen_env = genGenEnv(req.body); fs.writeFileSync(path.join(__dirname, "gen.env"), gen_env, (err) => { if (err) { @@ -31,171 +32,167 @@ router.post("/submit", (req, res) => { [valid, genOut] = callExec(`node gen.js`); - console.log() - console.log(genOut) - - if (!valid){ - res.render("submit", { message: "failed, please try again", error: genOut }) - return + console.log(); + console.log(genOut); + + if (!valid) { + res.render("submit", { + message: "failed, please try again", + error: genOut, + }); + return; } - res.render("submit", { message: "Config generation success, please follow instructions in generated/commands.txt" }); - process.exit() + res.render("submit", { + message: + "Config generation success, please follow instructions in generated/commands.txt", + }); + process.exit(); }); router.post("/confirm", (req, res) => { - console.log(req.body) + console.log(req.body); res.render("confirm", { title: "Hey", message: "this is submit" }); }); -router.get("/address", (req,res) => { - const randomWallet = ethers.Wallet.createRandom() +router.get("/address", (req, res) => { + const randomWallet = ethers.Wallet.createRandom(); res.json({ - "publicKey": randomWallet.address, - "privateKey": randomWallet.privateKey + publicKey: randomWallet.address, + privateKey: randomWallet.privateKey, }); -}) - +}); + app.use("/", router); app.listen(process.env.port || 5210); - -console.log("Running at Port 5210"); +console.log("Running at Port 5210"); function callExec(command) { try { - const stdout = execSync(command, { timeout: 200000, encoding: 'utf-8'}); + const stdout = execSync(command, { timeout: 200000, encoding: "utf-8" }); output = stdout.toString(); // console.log(output); - return [true, output] + return [true, output]; } catch (error) { // console.log(error) - return [false, error.stdout] + return [false, error.stdout]; throw Error(error.stdout); } } -function genGenEnv(input){ - console.log(input) - - let os = '' - let content_os = '' - switch (input.osradio){ - case 'os-radio-mac': - os = 'mac' - content_os += `\nMAIN_IP=127.0.0.1` - content_os += `\nNUM_MACHINE=1` - break - case 'os-radio-linux': - os = 'linux' - content_os += `\nMAIN_IP=${input["text-private-ip"]}` - if (input["text-public-ip"] != ''){ - content_os += `\nPUBLIC_IP=${input["text-public-ip"]}` - } - if (input["text-num-machine"] != ''){ - content_os += `\nNUM_MACHINE=${input["text-num-machine"]}` - } else { - content_os += `\nNUM_MACHINE=1` - } - break - } - - let parentnet = '' - switch (input.pnradio){ - case 'pn-radio-testnet': - parentnet = 'testnet' - break - case 'pn-radio-devnet': - parentnet = 'devnet' - break - case 'pn-radio-mainnet': - parentnet = 'mainnet' - break - } - - let relayer_mode = '' - switch (input.rmradio){ - case 'rm-radio-full': - relayer_mode = 'full' - break - case 'rm-radio-lite': - relayer_mode = 'lite' - break - } - - let content_custom_key = '' - if (input["grandmaster-pk"] != ''){ - content_custom_key += `\nGRANDMASTER_PK=${input["grandmaster-pk"]}` - } - - let subnet_keys=[] - let idx = 1 - while ('subnet-key'+idx.toString() in input){ - key = 'subnet-key'+idx.toString() - subnet_keys.push(input[key]) - idx++ - } - if (subnet_keys.length > 0){ - key_string = subnet_keys.join(',') - content_custom_key += `\nSUBNETS_PK=${key_string}` - } - - let content_version = '' - if (input["customversion-subnet"] != ''){ - content_version += `\nVERSION_SUBNET=${input["customversion-subnet"]}` - } - if (input["customversion-bootnode"] != ''){ - content_version += `\nVERSION_BOOTNODE=${input["customversion-bootnode"]}` - } - if (input["customversion-relayer"] != ''){ - content_version += `\nVERSION_RELAYER=${input["customversion-relayer"]}` - } - if (input["customversion-stats"] != ''){ - content_version += `\nVERSION_STATS=${input["customversion-stats"]}` - } - if (input["customversion-frontend"] != ''){ - content_version += `\nVERSION_FRONTEND=${input["customversion-frontend"]}` - } - if (input["customversion-csc"] != ''){ - content_version += `\nVERSION_CSC=${input["customversion-csc"]}` - } - if (input["customversion-zero"] != ''){ - content_version += `\nVERSION_ZERO=${input["customversion-zero"]}` - } - - let content_zero = '' - if (relayer_mode == 'full' && 'xdczero-checkbox' in input){ - if (input["zmradio"] == 'zm-radio-one'){ - content_zero += '\nXDC_ZERO=one-directional' +function genGenEnv(input) { + console.log(input); + + let content_machine = ""; + if (input["text-num-machine"] > 1) { + content_machine += `\nMAIN_IP=${input["text-private-ip"]}`; + if (input["text-public-ip"] != "") { + content_machine += `\nPUBLIC_IP=${input["text-public-ip"]}`; + } + if (input["text-num-machine"] != "") { + content_machine += `\nNUM_MACHINE=${input["text-num-machine"]}`; + } + } else { + content_machine += `\nMAIN_IP=127.0.0.1`; + content_machine += `\nNUM_MACHINE=1`; + } + + let parentnet = ""; + switch (input.pnradio) { + case "pn-radio-testnet": + parentnet = "testnet"; + break; + case "pn-radio-devnet": + parentnet = "devnet"; + break; + case "pn-radio-mainnet": + parentnet = "mainnet"; + break; + } + + let relayer_mode = ""; + switch (input.rmradio) { + case "rm-radio-full": + relayer_mode = "full"; + break; + case "rm-radio-lite": + relayer_mode = "lite"; + break; + } + + let content_custom_key = ""; + if (input["grandmaster-pk"] != "") { + content_custom_key += `\nGRANDMASTER_PK=${input["grandmaster-pk"]}`; + } + + let subnet_keys = []; + let idx = 1; + while ("subnet-key" + idx.toString() in input) { + key = "subnet-key" + idx.toString(); + subnet_keys.push(input[key]); + idx++; + } + if (subnet_keys.length > 0) { + key_string = subnet_keys.join(","); + content_custom_key += `\nSUBNETS_PK=${key_string}`; + } + + let content_version = ""; + if (input["customversion-subnet"] != "") { + content_version += `\nVERSION_SUBNET=${input["customversion-subnet"]}`; + } + if (input["customversion-bootnode"] != "") { + content_version += `\nVERSION_BOOTNODE=${input["customversion-bootnode"]}`; + } + if (input["customversion-relayer"] != "") { + content_version += `\nVERSION_RELAYER=${input["customversion-relayer"]}`; + } + if (input["customversion-stats"] != "") { + content_version += `\nVERSION_STATS=${input["customversion-stats"]}`; + } + if (input["customversion-frontend"] != "") { + content_version += `\nVERSION_FRONTEND=${input["customversion-frontend"]}`; + } + if (input["customversion-csc"] != "") { + content_version += `\nVERSION_CSC=${input["customversion-csc"]}`; + } + if (input["customversion-zero"] != "") { + content_version += `\nVERSION_ZERO=${input["customversion-zero"]}`; + } + + let content_zero = ""; + if (relayer_mode == "full" && "xdczero-checkbox" in input) { + if (input["zmradio"] == "zm-radio-one") { + content_zero += "\nXDC_ZERO=one-directional"; } - if (input["zmradio"] == 'zm-radio-bi'){ - content_zero += '\nXDC_ZERO=bi-directional' - content_zero += `\nSUBNET_WALLET_PK=${input["subnet-wallet-pk"]}` - content_zero += `\nSUBNET_ZERO_WALLET_PK=${input["subnet-zero-wallet-pk"]}` + if (input["zmradio"] == "zm-radio-bi") { + content_zero += "\nXDC_ZERO=bi-directional"; + content_zero += `\nSUBNET_WALLET_PK=${input["subnet-wallet-pk"]}`; + content_zero += `\nSUBNET_ZERO_WALLET_PK=${input["subnet-zero-wallet-pk"]}`; } - content_zero += `\nPARENTNET_ZERO_WALLET_PK=${input["parentnet-zero-wallet-pk"]}` - if ('subswap-checkbox' in input){ - content_zero += '\nSUBSWAP=true' + content_zero += `\nPARENTNET_ZERO_WALLET_PK=${input["parentnet-zero-wallet-pk"]}`; + if ("subswap-checkbox" in input) { + content_zero += "\nSUBSWAP=true"; } } - content=` + content = ` NETWORK_NAME=${input["text-subnet-name"]} NUM_SUBNET=${input["text-num-subnet"]} -OS=${os} PARENTNET=${parentnet} PARENTNET_WALLET_PK=${input["parentnet-wallet-pk"]} RELAYER_MODE=${relayer_mode} -` - content+=content_os - content+='\n' - content+=content_custom_key - content+='\n' - content+=content_version - content+='\n' - content+=content_zero - - console.log(content) - - return content +`; + content += content_machine; + content += "\n"; + content += content_custom_key; + content += "\n"; + content += content_version; + content += "\n"; + content += content_zero; + + console.log(content); + + return content; } diff --git a/deployment-generator/src/views/custom.css b/deployment-generator/src/views/custom.css index 05f578f..2410391 100644 --- a/deployment-generator/src/views/custom.css +++ b/deployment-generator/src/views/custom.css @@ -4,15 +4,15 @@ max-width: 1100px; } -#helper{ +#helper { margin-left: 15%; margin-top: 50px; max-width: 1100px; } -#helper-title{ - margin-right:10%; +#helper-title { + margin-right: 10%; } -.helper-block{ +.helper-block { padding: 10px; } @@ -28,17 +28,16 @@ display: block; } -#toggle2:checked ~ .grayedout{ - background-color: gray - disabled +#toggle2:checked ~ .grayedout { + background-color: gray disabled; } -#lite-mode-extra-info{ - color:red +#lite-mode-extra-info { + color: red; } -#incomplete-required-warning{ - color:red +#incomplete-required-warning { + color: red; } /* hover trick */ @@ -59,7 +58,7 @@ margin-left: 5px; cursor: pointer; } -.info-icon-all-gray{ +.info-icon-all-gray { color: #f0f0f0; background-color: #f0f0f0; } @@ -99,41 +98,43 @@ opacity: 1; } - -#information1{ - display:none; +#information1 { + display: none; } -#expand1:hover + #information1{ - display:block; +#expand1:hover + #information1 { + display: block; } /* expanding after selection */ -#linuxoption{ - display:none; +#ipoption { + display: none; } -#os-radio-linux:checked+#linuxoption{ - display:block; +#checkbox-num-machine { + display: none; +} +#checkbox-num-machine:checked + #ipoption { + display: block; } -#customkeys{ - display:none; +#customkeys { + display: none; } -#customkeys-checkbox:checked+#customkeys{ - display:block; +#customkeys-checkbox:checked + #customkeys { + display: block; } -#customversion{ - display:none; +#customversion { + display: none; } -#customversion-checkbox:checked+#customversion{ - display:block; +#customversion-checkbox:checked + #customversion { + display: block; } -#xdczero{ - display:none; +#xdczero { + display: none; } -#xdczero-checkbox:checked+#xdczero{ - display:block; +#xdczero-checkbox:checked + #xdczero { + display: block; } /* #rm-radio-full:checked ~ #xdczero-upper{ @@ -143,12 +144,12 @@ display:block; } */ -#zerobidi{ - display:none; +#zerobidi { + display: none; } -#zm-radio-bi:checked ~ #zerobidi{ - display:block; +#zm-radio-bi:checked ~ #zerobidi { + display: block; +} +#zm-radio-one:checked ~ #zerobidi { + display: none; } -#zm-radio-one:checked ~ #zerobidi{ - display:none; -} \ No newline at end of file diff --git a/deployment-generator/src/views/index.pug b/deployment-generator/src/views/index.pug index 03fab04..f2c6207 100644 --- a/deployment-generator/src/views/index.pug +++ b/deployment-generator/src/views/index.pug @@ -30,45 +30,48 @@ body label() Number of Subnet Nodes input#text-num-subnet(type='number' placeholder='3' name="text-num-subnet") span.pure-form-message-inline Required - + .pure-control-group - .info-container + .info-container .info-icon(tabindex="0" role="button" aria-label="more info") ? - .tooltip - p - WSL is not supported yet - p - Mac is only suitable for testing purposes - label() Operating System - br - br - label.pure-radio(for='os-radio-mac') - input#os-radio-mac(type='radio' name='osradio' value='os-radio-mac' checked) - | Mac - br - br - label.pure-radio(for='os-radio-linux') - input#os-radio-linux(type='radio' name='osradio' value='os-radio-linux') - | Linux - #linuxoption.content - .pure-control-group - .info-container - .info-icon(tabindex="0" role="button" aria-label="more info") ? - .tooltip Private IP is used for Subnet nodes communication. - label() Private IP - input#text-private-ip(type='text' placeholder='192.168.1.1' name="text-private-ip") - span.pure-form-message-inline Required. - .pure-control-group - .info-container - .info-icon(tabindex="0" role="button" aria-label="more info") ? - .tooltip Public IP is used for accessing Subnet services, eg. Frontend. - label() Public IP - input#text-public-ip(type='text' placeholder='1.1.1.1' name="text-public-ip") - .pure-control-group - .info-container - .info-icon(tabindex="0" role="button" aria-label="more info") ? - .tooltip The generated configs will evenly spread the nodes across the machines. - label() Number of Machines - input#text-num-machine(type='number' placeholder='1' name="text-num-machine") - + .tooltip The generated configs will evenly spread the nodes across the machines. + label() Number of Machines + input#text-num-machine(type='number' placeholder='1' name="text-num-machine" oninput="numMachineMoreThanOne()") + + input#checkbox-num-machine(type='checkbox' name='checkbox-num-machine') + + //- .pure-control-group + //- .info-container + //- .info-icon(tabindex="0" role="button" aria-label="more info") ? + //- .tooltip + //- p - WSL is not supported yet + //- p - Mac is only suitable for testing purposes + //- label() Operating System + //- br + //- br + //- label.pure-radio(for='os-radio-mac') + //- input#os-radio-mac(type='radio' name='osradio' value='os-radio-mac' checked) + //- | Mac + //- br + //- br + //- label.pure-radio(for='os-radio-linux') + //- input#os-radio-linux(type='radio' name='osradio' value='os-radio-linux') + //- | Linux + #ipoption.content + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Private IP is used for Subnet nodes communication. + label() Private IP + input#text-private-ip(type='text' placeholder='192.168.1.1' name="text-private-ip") + span.pure-form-message-inline Required + .pure-control-group + .info-container + .info-icon(tabindex="0" role="button" aria-label="more info") ? + .tooltip Public IP is used for accessing Subnet services, eg. Frontend. + label() Public IP + input#text-public-ip(type='text' placeholder='1.1.1.1' name="text-public-ip") + @@ -293,6 +296,20 @@ script. } } } + function numMachineMoreThanOne(){ + const num = document.getElementById("text-num-machine") + const hiddenbox = document.getElementById("checkbox-num-machine") + if (num.value > 1){ + hiddenbox.checked = true; + } else { + hiddenbox.checked = false; + const text1 = document.getElementById("text-private-ip") + const text2 = document.getElementById("text-public-ip") + text1.value = "" + text2.value = "" + } + } + function relayerLiteClicked(radio){ const checkbox = document.getElementById("xdczero-checkbox") checkbox.checked = false; @@ -309,15 +326,15 @@ script. const form = document.forms["myForm"] const name = form["text-subnet-name"].value; - const num = form["text-num-subnet"].value; + const num_subnet = form["text-num-subnet"].value; const pn_pk = form["parentnet-wallet-pk"].value; - if (name == "" || num == "" || pn_pk == ""){ + if (name == "" || num_subnet == "" || pn_pk == ""){ document.getElementById("incomplete-required-warning").innerHTML="Please fill in all required fields" return false } - const linux = form["os-radio-linux"].checked; - if (linux){ + const num_machines = form["text-num-machine"].value; + if (num_machines > 1){ const private_ip = form["text-private-ip"].value; if (private_ip == ""){ document.getElementById("incomplete-required-warning").innerHTML="Please fill in all required fields" diff --git a/deployment-generator/src/views_faucet/custom.css b/deployment-generator/src/views_faucet/custom.css index c3ef3e0..e217b06 100644 --- a/deployment-generator/src/views_faucet/custom.css +++ b/deployment-generator/src/views_faucet/custom.css @@ -4,20 +4,18 @@ max-width: 1100px; } - -#helper{ +#helper { margin-left: 15%; margin-top: 50px; max-width: 1100px; } -#helper-title{ - margin-right:10%; +#helper-title { + margin-right: 10%; } -.helper-block{ +.helper-block { padding: 10px; } - #final { margin-left: 175px; margin-top: 20px; @@ -26,4 +24,3 @@ #error { color: red; } - \ No newline at end of file