Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1ddde82
genesis-to-chainspec v1
wanwiset25 Jul 5, 2026
d8a61d5
bump nodejs to 24
wanwiset25 Jul 26, 2026
d7f6d02
add readme
wanwiset25 Jul 26, 2026
3172f54
add xdpos only mode to prevent going to the wrong url
wanwiset25 Jul 26, 2026
9a2f06b
update genesis to chainspec converter
wanwiset25 Jul 26, 2026
8422792
fix bootnode ip to .254
wanwiset25 Jul 26, 2026
3d43669
add nethermind ui option and parse to configs
wanwiset25 Jul 26, 2026
6e162f9
integrate nethermind node in generator
wanwiset25 Jul 27, 2026
88d6325
remove old source files path
wanwiset25 Jul 28, 2026
c1b17bc
add back button when failed, since container hasn't shut down yet
wanwiset25 Aug 16, 2026
02177eb
- allow chainspec converter to be called as a container
wanwiset25 Aug 16, 2026
d13b8bb
add chainspec check before every startup to prevent genesis.json <> c…
wanwiset25 Aug 16, 2026
7329ad9
improve version detection of chainspec checker and organize directories
wanwiset25 Aug 16, 2026
945c1de
update default
wanwiset25 Aug 16, 2026
5aa223c
fix bug
wanwiset25 Aug 16, 2026
91d1a66
update genesis-to-chainspec.js to read values from genesis.json inste…
wanwiset25 Aug 16, 2026
6026e7c
improve wording
wanwiset25 Aug 16, 2026
971feb2
for a single machine setup, make docker-up.sh and docker-down.sh to n…
wanwiset25 Aug 16, 2026
0c6d2c6
improve startup and update docs
wanwiset25 Aug 24, 2026
5252c0b
carry genesis reward amounts into chainspec; fix generator image refs
wanwiset25 Aug 24, 2026
298a71c
auto docker pull to make sure the generator image is updated
wanwiset25 Aug 24, 2026
bba78f6
add prague processing to genesis-to-chainspec.js
wanwiset25 Aug 24, 2026
61be76e
update forks in genesis-to-chainspec converter
wanwiset25 Aug 25, 2026
94f4e45
update genesis converter
wanwiset25 Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ mount
**temp
*key*
*keys*
test

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Do not ignore every test path.

A slashless test pattern recursively ignores any file or directory with that name, which can silently exclude test source. Remove it or scope it to the intended generated artifact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore at line 13, Update the .gitignore entry containing the slashless
test pattern so it no longer recursively ignores every file or directory named
test. Remove the broad pattern or scope it specifically to the intended
generated artifact, preserving visibility of test source files.

2 changes: 1 addition & 1 deletion container-manager/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Use the official Node.js 18.15 image as the base
FROM node:18.15
FROM node:24

# Install Docker CLI (required to interact with the Docker API)
RUN apt-get update && apt-get install -y \
Expand Down
1 change: 1 addition & 0 deletions container-manager/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npm run dev
40 changes: 40 additions & 0 deletions container-manager/src/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,46 @@ app.use(
);
app.use(express.json());

// NON_SUBNET mode: when NON_SUBNET=true, block every subnet-management route
// (including the wizard landing page "/") so only the XDPoS private-network
// generator (/gen_xdpos, /submit_xdpos) remains reachable.
const NON_SUBNET = process.env.NON_SUBNET === "true";
const SUBNET_ROUTES = [
"/",
"/debug",
"/state",
"/deploy_csc_lite",
"/deploy_csc_full",
"/deploy_csc_reversefull",
"/deploy_zero",
"/deploy_subswap",
"/start_subnet",
"/stop_subnet",
"/start_subnet_slow",
"/start_services",
"/stop_services",
"/start_subswap_frontend",
"/stop_subswap_frontend",
"/start_explorer",
"/stop_explorer",
"/remove_subnet",
"/gen",
"/submit",
"/submit_preconfig",
"/faucet",
"/faucet_subnet",
];

app.use((req, res, next) => {
if (NON_SUBNET && SUBNET_ROUTES.includes(req.path)) {
console.log(`NON_SUBNET: blocked ${req.path}`);
return res
.status(403)
.json({ error: "Subnet routes are disabled (NON_SUBNET mode)" });
}
next();
Comment on lines +55 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching express.js:\n'
fd -a 'express\.js$' . || true

printf '\nRelevant file excerpts:\n'
for f in $(fd 'express\.js$' .); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,140p' "$f" | cat -n
done

printf '\nSubnet route declarations/usages:\n'
rg -n "SUBNET_ROUTES|/submit|submit|strictRouting|strict routing" container-manager/src || true

printf '\nPackage Express version if available:\n'
fd 'package\.json$' -d 5 . | xargs -r -n1 sh -c 'echo "--- $0"; jq ".dependencies.express, .devDependencies.express" "$0"'

Repository: XinFinOrg/Subnet-Deployment

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Package.json/lock Express entries (if present):\n'
for f in $(fd 'package(-lock)?\.json$' -d 4 .); do
  echo "--- $f"
  jq -r '(.dependencies.express?, .devDependencies.express? // keys[]?|select(startswith("express@"))? // "none", (.["express@*"] // {})|keys[]?|select(startswith("express@"))? // "none")|select(type=="string")' "$f" 2>/dev/null || true
done

printf '\nRest of express.js route handlers and strict settings:\n'
sed -n '140,225p' container-manager/src/express.js | cat -n
grep -En "strictRouting|app\(strict|strict\(\)" container-manager/src || true

printf '\nProgrammatic simulation of current middleware vs Express-style non-strict GET routes for /submit and /submit/.submit variants:\n'
python3 - <<'PY'
BLOCKED = ["/submit", "/submit_preconfig", "/debug", "/"]
def current_checks(path):
    return path in BLOCKED
def express_like_matches(handler_path, req_path):
    if handler_path.endswith("/"):
        return req_path == handler_path or (handler_path[:-1] == req_path)
    return req_path == handler_path or req_path == handler_path + "/"

handler = "/submit"
for req_path in ["/submit", "/submit/", "/submit.submit", "/submit/submit", "/debug/"]:
    print(f"{req_path!r}: blocked={current_checks(req_path)} matching={express_like_matches(handler, req_path)}")
PY

Repository: XinFinOrg/Subnet-Deployment

Length of output: 3664


🌐 Web query:

Express.js 4.x 5.x strictRouting trailing slashes /foo and /foo/ route matching

💡 Result:

In Express.js, both version 4.x and 5.x provide a strict routing option that controls how the router handles trailing slashes in request paths [1][2]. By default, the strict routing option is disabled (false) [1][2]. When it is disabled, Express treats paths with and without a trailing slash (e.g., /foo and /foo/) as identical, meaning they will match the same route [1][2]. When the strict routing option is enabled (true), the router treats /foo and /foo/ as distinct, different routes [1][3]. To enable this behavior, you can pass the strict: true option when creating a router instance [1][2]: const router = express.Router({ strict: true }); This setting applies to the specific router instance. If you are using the main application object as a router, note that Express applications do not typically expose a direct configuration option for strict routing at the app level in the same way, though you can use a mounted router to enforce this behavior for specific sub-paths [1][2].

Citations:


Normalize paths before checking blocked routes.

Exact matching lets trailing-slash variants such as /submit/ bypass the middleware while Express still dispatches them to the /submit handler. Strip a single trailing slash from every non-root path before SUBNET_ROUTES.includes(...) and add coverage for the blocked route variants.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 56-56: Logging request-derived input unsanitized allows log forging (CRLF injection); strip newlines / encode the value before logging.
Context: console.log(NON_SUBNET: blocked ${req.path})
Note: [CWE-117] Improper Output Neutralization for Logs. OWASP A09:2021 Security Logging and Monitoring Failures.

(log-injection-javascript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@container-manager/src/express.js` around lines 55 - 62, Update the middleware
registered with app.use so it normalizes req.path before SUBNET_ROUTES.includes
by removing one trailing slash from non-root paths, ensuring variants like
/submit/ remain blocked while / is unchanged. Add coverage for blocked routes
with and without the trailing slash.

});

app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "views", "index.html"));
});
Expand Down
2 changes: 2 additions & 0 deletions container-manager/src/gen/config_gen.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const config = {
deployment_path: process.env.CONFIG_PATH || "",
num_machines: parseInt(process.env.NUM_MACHINE),
num_subnet: parseInt(process.env.NUM_SUBNET),
num_nethermind: parseInt(process.env.NUM_NETHERMIND) || 0,
ip_1: process.env.MAIN_IP || "",
public_ip: process.env.PUBLIC_IP || process.env.MAIN_IP,
network_name: process.env.NETWORK_NAME,
Expand Down Expand Up @@ -56,6 +57,7 @@ const config = {
},
xdpos: {
xdposnode: process.env.VERSION_NODE_IMAGE || "xinfinorg/devnet:dev-upgrade-53e5601",
nethermind: process.env.VERSION_NETHERMIND_IMAGE || "nethermindeth/nethermind:master-857da8f",
stake_threshold: parseInt(process.env.MASTERNODE_MINIMUM_STAKE) || "",
reward_yield: parseInt(process.env.REWARDS_YIELD) || "",
foundation_addr: "",
Expand Down
4 changes: 3 additions & 1 deletion container-manager/src/gen/gen_compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ function injectNetworkConfig(compose_object) {
Object.entries(compose_object["services"]).forEach((entry) => {
const [key, value] = entry;
let component_ip;
if (key.startsWith("subnet")) {
if (key === "bootnode") {
component_ip = ip_string_base + "254"; // fixed, predictable bootnode IP
} else if (key.startsWith("subnet")) {
component_ip = ip_string_base + parseInt(start_ip_subnet);
start_ip_subnet += 1;
} else {
Expand Down
139 changes: 115 additions & 24 deletions container-manager/src/gen/gen_xdpos.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,26 @@ doc["services"]["bootnode"] = {

// checkpoint smartcontract deployment config
doc, (ip_record = gen_compose.injectNetworkConfig(doc));

// bootnode enode for bootnodes.list — set here so it is populated even when
// every masternode runs Nethermind (and genXdposNodeConfig is never called).
const bootnode_ip =
config.num_machines === 1 ? ip_record["bootnode"] : config.ip_1;
bootnode = `enode://cc566d1033f21c7eb0eb9f403bb651f3949b5f63b40683917765c343f9c0c596e9cd021e2e8416908cbc3ab7d6f6671a83c85f7b121c1872f8be50a591723a5d@${bootnode_ip}:20301\n`;

subnetconf = [];
for (let i = 1; i <= config.num_subnet; i++) {
subnetconf.push(genXdposNodeConfig(i, keys, ip_record));
if (isNethermindNode(i)) {
subnetconf.push({
filename: `masternode${i}nmc.env`,
content: genNethermindNodeConfig(i, keys, ip_record),
});
} else {
subnetconf.push({
filename: `masternode${i}.env`,
content: genXdposNodeConfig(i, keys, ip_record),
});
}
}

const compose_content = yaml.dump(doc, {});
Expand Down Expand Up @@ -123,8 +140,8 @@ function writeGenerated(output_dir) {

for (let i = 1; i <= config.num_subnet; i++) {
fs.writeFileSync(
`${output_dir}/masternode${i}.env`,
subnetconf[i - 1],
`${output_dir}/${subnetconf[i - 1].filename}`,
subnetconf[i - 1].content,
(err) => {
if (err) {
console.error(err);
Expand Down Expand Up @@ -169,6 +186,14 @@ function copyScripts(output_dir) {
`${__dirname}/scripts/docker-down.sh`,
`${output_dir}/docker-down.sh`
);
if (config.num_nethermind > 0) {
// shared Nethermind config mounted by every nmc node (chainspec.json is
// produced separately from genesis.json after puppeth runs)
fs.copyFileSync(
`${__dirname}/scripts/xdc-nmc.json`,
`${output_dir}/xdc-nmc.json`
);
}
}

function initConfig(config) {
Expand All @@ -182,6 +207,11 @@ function initConfig(config) {
process.exit(1);
}

if (config.num_nethermind < 0 || config.num_nethermind > config.num_subnet) {
console.log("NUM_NETHERMIND must be between 0 and NUM_SUBNET");
process.exit(1);
}

if (net.isIP(config.main_ip) != 0) {
console.log("MAIN_IP Invalid IP address");
process.exit(1);
Expand Down Expand Up @@ -334,12 +364,46 @@ GC_MODE=archive
PORT=${port}
RPC_PORT=${rpcport}
WS_PORT=${wsport}
LOG_LEVEL=2
LOG_LEVEL=4
`;

return config_env;
}

// The last `num_nethermind` masternodes run the Nethermind client instead of
// the Go client. They stay validators and reuse the same key/port/IP slot.
function isNethermindNode(subnet_id) {
return subnet_id > config.num_subnet - config.num_nethermind;
}

// Per-node env file (masternode<i>nmc.env) — Nethermind reads NETHERMIND_* env
// vars (format NETHERMIND_<CATEGORY>CONFIG_<PROPERTY>). Shared/static settings
// live in xdc-nmc.json; only per-node values are emitted here.
function genNethermindNodeConfig(subnet_id, key, ip_record) {
const private_key = key[`key${subnet_id}`]["PrivateKey"]; // 0x-prefixed
const port = 20302 + subnet_id; // P2P + discovery
const rpcport = 8544 + subnet_id; // JSON-RPC
const ip = ip_record[`masternode${subnet_id}`];
const config_env = `
NETHERMIND_JSONRPCCONFIG_ENABLED=true
NETHERMIND_JSONRPCCONFIG_HOST=0.0.0.0
NETHERMIND_JSONRPCCONFIG_PORT=${rpcport}
NETHERMIND_NETWORKCONFIG_P2PPORT=${port}
NETHERMIND_NETWORKCONFIG_DISCOVERYPORT=${port}
NETHERMIND_NETWORKCONFIG_EXTERNALIP=${ip}
NETHERMIND_NETWORKCONFIG_FILTERPEERSBYRECENTIP=false
NETHERMIND_NETWORKCONFIG_BOOTNODES=${bootnode.trim()}
NETHERMIND_INITCONFIG_DISCOVERYENABLED=true
NETHERMIND_MININGCONFIG_ENABLED=true
NETHERMIND_KEYSTORECONFIG_TESTNODEKEY=${private_key}
NETHERMIND_HEALTHCHECKSCONFIG_ENABLED=true
NETHERMIND_METRICSCONFIG_ENABLED=true
NETHERMIND_METRICSCONFIG_EXPOSEPORT=8009
NO_COLOR=1
`;
return config_env;
}


function genXdposCompose(machine_id, num, start_num = 1) {
let nodes = {};
Expand All @@ -350,26 +414,53 @@ function genXdposCompose(machine_id, num, start_num = 1) {
const port = 20302 + i;
const rpcport = 8544 + i;
const wsport = 9554 + i;

imageName = `${config.xdpos.xdposnode}`;
config_path = "masternode" + i.toString() + ".env";

nodes[node_name] = {
image: imageName,
volumes: [volume, "./genesis.json:/work/genesis.json", "./bootnodes.list:/work/bootnodes.list"],
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`,
],
};
const port_mappings = [
`${port}:${port}/tcp`,
`${port}:${port}/udp`,
`${rpcport}:${rpcport}/tcp`,
`${rpcport}:${rpcport}/udp`,
`${wsport}:${wsport}/tcp`,
`${wsport}:${wsport}/udp`,
];

if (isNethermindNode(i)) {
// Nethermind validator: config via masternode<i>nmc.env (NETHERMIND_*)
// + shared xdc-nmc.json; uses chainspec.json instead of genesis.json.
// injectNetworkConfig() adds the bridge networks/ipv4_address block.
nodes[node_name] = {
image: `${config.xdpos.nethermind}`,
volumes: [
volume,
"./chainspec.json:/work/chainspec.json",
"./xdc-nmc.json:/work/xdc-nmc.json",
"./bootnodes.list:/work/bootnodes.list",
],
restart: "always",
env_file: [`masternode${i}nmc.env`],
command: [
"--config=/work/xdc-nmc.json",
"--datadir=/work/xdcchain",
"--log=debug",
],
profiles: [compose_profile],
ports: port_mappings,
};
} else {
// Go XDPoS masternode (default).
nodes[node_name] = {
image: `${config.xdpos.xdposnode}`,
volumes: [
volume,
"./genesis.json:/work/genesis.json",
"./bootnodes.list:/work/bootnodes.list",
],
restart: "always",
network_mode: "host",
env_file: [`masternode${i}.env`],
profiles: [compose_profile],
ports: port_mappings,
};
}
}
return nodes;
}
Expand Down
33 changes: 33 additions & 0 deletions container-manager/src/gen/scripts/xdc-nmc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json",
"Init": {
"ChainSpecPath": "/work/chainspec.json",
"BaseDbPath": "nethermind_db/xdc-nmc",
"LogFileName": "xdc.log",
"StateDbKeyScheme": "Hash",
"AutoDump": "All"
},
"TxPool": {
"BlobsSupport": "Disabled"
},
"Sync": {
"FastSync": true,
"NeedToWaitForHeader": true,
"VerifyTrieOnStateSyncFinished": true
},
"Mining": {
"Enabled": true
},
"JsonRpc": {
"Enabled": true
},
"Blocks": {
"TargetBlockGasLimit": 50000000
},
"Merge": {
"Enabled": false
},
"TraceStore": {
"Enabled": false
}
}
35 changes: 35 additions & 0 deletions container-manager/src/libs/exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,33 @@ function generateXdpos(params) {
command = `cd ${mountPath}; docker run -v ${config.hostPath}:/app/generated/ --entrypoint 'bash' ${versionGenesisFullname} /work/puppeth.sh`;
console.log(command);
const [result2, out2] = callExec(command);
if (!result2) {
return [result2, out2];
}

//step 3: convert genesis.json -> chainspec.json (needed by Nethermind nodes)
const nethermindCount =
"customversion-checkbox" in params &&
params["customversion-checkbox"] != ""
? parseInt(params["customversion-xdpos-nethermind-count"]) || 0
: 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file sizes =="
wc -l container-manager/src/libs/exec.js container-manager/src/views/xdpos_generator/index.pug

echo "== relevant exec.js sections =="
sed -n '160,210p' container-manager/src/libs/exec.js
sed -n '380,420p' container-manager/src/libs/exec.js

echo "== relevant pug section =="
sed -n '80,100p' container-manager/src/views/xdpos_generator/index.pug

echo "== JS parse/runtime behavior probe =="
node - <<'JS'
const input = { "customversion-checkbox": "on", "customversion-xdpos-nethermind-count": "1e3" };
const nethermindCount =
  "customversion-checkbox" in input &&
  input["customversion-checkbox"] != ""
    ? parseInt(input["customversion-xdpos-nethermind-count"]) || 0
    : 0;

const emissionCondition = "customversion-checkbox" in input && input["customversion-checkbox"] != "" && "customversion-xdpos-nethermind-count" in input && input["customversion-xdpos-nethermind-count"] != "";
console.log({
  parseIntValue: nethermindCount,
  emissionCondition,
  emittedLine: emissionCondition ? `NUM_NETHERMIND=${input["customversion-xdpos-nethermind-count"]}` : "",
  isNaNNegative: isNaN(parseInt(input["customversion-xdpos-nethermind-count"])),
});

for (const value of ["-5", "", "abc", "3.7", "9007199254740991", "9007199254740992"]) {
  console.log(value, parseInt(value, 10));
}
JS

echo "== search for Nethermind count handling =="
rg -n "customversion-xdpos-nethermind-count|NUM_NETHERMIND|nethermind" container-manager/src

Repository: XinFinOrg/Subnet-Deployment

Length of output: 8224


Validate and canonicalize the Nethermind count before storing/updating it.

parseInt(params["...-count"]) can disagree with the raw NUM_NETHERMIND value, and negative values bypass chainspec generation while still being written to the environment. Server-side normalize the count to a non-negative safe integer (or reject invalid values), write that normalized value to NUM_NETHERMIND, and add min='0'/step='1' to the form input.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require("child_process")
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

📍 Affects 2 files
  • container-manager/src/libs/exec.js#L188-L192 (this comment)
  • container-manager/src/libs/exec.js#L404-L406
  • container-manager/src/views/xdpos_generator/index.pug#L90-L91
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@container-manager/src/libs/exec.js` around lines 188 - 192, The Nethermind
count is parsed inconsistently and can allow negative or unsafe values into
configuration. In container-manager/src/libs/exec.js at lines 188-192, normalize
or reject the count as a non-negative safe integer before storing it; at lines
404-406, write that same normalized value to NUM_NETHERMIND and use it for
chainspec generation. In container-manager/src/views/xdpos_generator/index.pug
at lines 90-91, add min='0' and step='1' to the count input.

if (nethermindCount > 0) {
try {
const { translate } = require("./genesis-to-chainspec");
const genesis = JSON.parse(
fs.readFileSync(path.join(mountPath, "genesis.json"), "utf-8")
);
const chainspec = translate(genesis, {});
fs.writeFileSync(
path.join(mountPath, "chainspec.json"),
JSON.stringify(chainspec, null, 2) + "\n"
);
console.log("chainspec.json generated");
} catch (e) {
console.error("chainspec generation failed:", e.message);
return [false, `chainspec generation failed: ${e.message}`];
}
}
return [result2, out2];
}

Expand Down Expand Up @@ -374,6 +401,14 @@ function genGenXdposEnv(input){
if ("customversion-checkbox" in input && input["customversion-checkbox"] != "" && "customversion-xdpos-node-fullname" in input && input["customversion-xdpos-node-fullname"] != "") {
content_version += `\nVERSION_NODE_IMAGE=${input["customversion-xdpos-node-fullname"]}`;
}
if ("customversion-checkbox" in input && input["customversion-checkbox"] != "" && "customversion-xdpos-nethermind-count" in input && input["customversion-xdpos-nethermind-count"] != "") {
content_version += `\nNUM_NETHERMIND=${input["customversion-xdpos-nethermind-count"]}`;
}
if ("customversion-checkbox" in input && input["customversion-checkbox"] != "" && "customversion-xdpos-nethermind-version" in input && input["customversion-xdpos-nethermind-version"] != "") {
content_version += `\nVERSION_NETHERMIND_IMAGE=${input["customversion-xdpos-nethermind-version"]}`;
}



let content_rewards = "";
if ("customrewards-checkbox" in input && input["customrewards-checkbox"] != "") {
Expand Down
Loading
Loading