@zombienet/orchestrator
Version:
ZombieNet aim to be a testing framework for substrate based blockchains, providing a simple cli tool that allow users to spawn and test ephemeral Substrate based networks
322 lines (321 loc) • 18.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateParachainFiles = generateParachainFiles;
const utils_1 = require("@zombienet/utils");
const fs_1 = __importDefault(require("fs"));
const chainSpec_1 = __importStar(require("./chainSpec"));
const configGenerator_1 = require("./configGenerator");
const constants_1 = require("./constants");
const chain_decorators_1 = require("./chain-decorators");
const providers_1 = require("./providers");
const client_1 = require("./providers/client");
const sharedTypes_1 = require("./sharedTypes");
const debug = require("debug")("zombie::paras");
function generateParachainFiles(namespace_1, tmpDir_1, parachainFilesPath_1, relayChainName_1, parachain_1, relayChainSpecIsRaw_1) {
return __awaiter(this, arguments, void 0, function* (namespace, tmpDir, parachainFilesPath, relayChainName, parachain, relayChainSpecIsRaw, random_sufix_to_isolate = null) {
var _a, _b, _c, _d, _e, _f, _g, _h;
const [addAuraAuthority, addAuthority, changeGenesisConfig, clearAuthorities, readAndParseChainSpec, specHaveSessionsKeys, getNodeKey, addParaCustom, addCollatorSelection, writeChainSpec,] = (0, chain_decorators_1.decorate)(parachain.para, [
chainSpec_1.default.addAuraAuthority,
chainSpec_1.default.addAuthority,
chainSpec_1.default.changeGenesisConfig,
chainSpec_1.default.clearAuthorities,
chainSpec_1.default.readAndParseChainSpec,
chainSpec_1.default.specHaveSessionsKeys,
chainSpec_1.default.getNodeKey,
chainSpec_1.default.addParaCustom,
chainSpec_1.default.addCollatorSelection,
chainSpec_1.default.writeChainSpec,
]);
const GENESIS_STATE_FILENAME_WITH_ID = `${constants_1.GENESIS_STATE_FILENAME}-${parachain.id}`;
const GENESIS_WASM_FILENAME_WITH_ID = `${constants_1.GENESIS_WASM_FILENAME}-${parachain.id}`;
const stateLocalFilePath = `${parachainFilesPath}/${constants_1.GENESIS_STATE_FILENAME}`;
const wasmLocalFilePath = `${parachainFilesPath}/${constants_1.GENESIS_WASM_FILENAME}`;
const client = (0, client_1.getClient)();
const { setupChainSpec, getChainSpecRaw } = providers_1.Providers.get(client.providerName);
let chainSpecFullPath;
const chainName = `${parachain.chain ? parachain.chain + "-" : ""}${parachain.name}-${relayChainName}`;
const chainSpecFileName = `${chainName}.json`;
const chainSpecFullPathPlain = `${tmpDir}/${chainName}-plain.json`;
if (parachain.cumulusBased) {
// need to create the parachain spec
// file name template is [para chain-]<para name>-<relay chain>
const relayChainSpecFullPathPlain = `${tmpDir}/${relayChainName}-plain.json`;
// Check if the chain-spec file is provided.
if (parachain.chainSpecPath) {
debug("parachain chain spec provided");
yield fs_1.default.promises.copyFile(parachain.chainSpecPath, chainSpecFullPathPlain);
}
else {
debug("creating chain spec plain");
// create or copy chain spec
yield setupChainSpec(namespace, {
chainSpecPath: parachain.chainSpecPath,
chainSpecCommand: parachain.chainSpecCommand,
defaultImage: parachain.collators[0].image,
}, parachain.chain, chainSpecFullPathPlain);
}
chainSpecFullPath = `${tmpDir}/${chainSpecFileName}`;
if (!(yield (0, chainSpec_1.isRawSpec)(chainSpecFullPathPlain))) {
// fields
const plainData = readAndParseChainSpec(chainSpecFullPathPlain);
const relayChainSpec = readAndParseChainSpec(relayChainSpecFullPathPlain);
if (plainData.para_id)
plainData.para_id = parachain.id;
if (plainData.paraId)
plainData.paraId = parachain.id;
if (plainData.relay_chain)
plainData.relay_chain = relayChainSpec.id;
if ((_b = (_a = plainData.genesis.runtime) === null || _a === void 0 ? void 0 : _a.parachainInfo) === null || _b === void 0 ? void 0 : _b.parachainId)
plainData.genesis.runtime.parachainInfo.parachainId = parachain.id;
else if ((_e = (_d = (_c = plainData.genesis.runtimeGenesis) === null || _c === void 0 ? void 0 : _c.patch) === null || _d === void 0 ? void 0 : _d.parachainInfo) === null || _e === void 0 ? void 0 : _e.parachainId)
plainData.genesis.runtimeGenesis.patch.parachainInfo.parachainId =
parachain.id;
else if ((_h = (_g = (_f = plainData.genesis.runtimeGenesis) === null || _f === void 0 ? void 0 : _f.config) === null || _g === void 0 ? void 0 : _g.parachainInfo) === null || _h === void 0 ? void 0 : _h.parachainId)
plainData.genesis.runtimeGenesis.config.parachainInfo.parachainId =
parachain.id;
writeChainSpec(chainSpecFullPathPlain, plainData);
// make genesis overrides first.
if (parachain.genesis)
yield changeGenesisConfig(chainSpecFullPathPlain, parachain.genesis);
// clear auths
yield clearAuthorities(chainSpecFullPathPlain);
// Chain spec customization logic
const addToSession = (node) => __awaiter(this, void 0, void 0, function* () {
const key = getNodeKey(node, false);
yield addAuthority(chainSpecFullPathPlain, node, key);
});
const addToAura = (node) => __awaiter(this, void 0, void 0, function* () {
yield addAuraAuthority(chainSpecFullPathPlain, node.name, node.accounts);
});
const addAuthFn = specHaveSessionsKeys(plainData)
? addToSession
: addToAura;
for (const node of parachain.collators) {
if (node.validator) {
yield addAuthFn(node);
yield addCollatorSelection(chainSpecFullPathPlain, node);
yield addParaCustom(chainSpecFullPathPlain, node);
}
}
debug("creating chain spec raw");
// ensure needed file
if (parachain.chain)
fs_1.default.copyFileSync(chainSpecFullPathPlain, `${tmpDir}/${parachain.chain}-${parachain.name}-plain.json`);
// Generate the raw chain-spec logic
// Make sure we include the plain chain-spec
const chainSpecRawCommand = getChainSpecCmdRaw(parachain.chainSpecCommand);
yield getChainSpecRaw(namespace, parachain.collators[0].image, `${parachain.chain ? parachain.chain + "-" : ""}${parachain.name}-${relayChainName}`, chainSpecRawCommand, chainSpecFullPath);
}
else {
console.log(`\n\t\t 🚧 ${utils_1.decorators.yellow(`Chain Spec for paraId ${parachain.id} was set to a file in raw format, can't customize.`)} 🚧`);
yield fs_1.default.promises.copyFile(chainSpecFullPathPlain, chainSpecFullPath);
}
try {
// ensure the correct para_id
const paraSpecRaw = readAndParseChainSpec(chainSpecFullPath);
if (paraSpecRaw.para_id)
paraSpecRaw.para_id = parachain.id;
if (paraSpecRaw.paraId)
paraSpecRaw.paraId = parachain.id;
// make chain unique if is set
if (random_sufix_to_isolate) {
// customize forkId/protocolId to make chain uniq
paraSpecRaw.forkId = `${paraSpecRaw.protocolId}${random_sufix_to_isolate}`;
paraSpecRaw.protocolId = `${paraSpecRaw.protocolId}${random_sufix_to_isolate}`;
}
writeChainSpec(chainSpecFullPath, paraSpecRaw);
}
catch (e) {
if (e.code !== "ERR_FS_FILE_TOO_LARGE")
throw e;
// can't customize para_id
console.log(`\n\t\t 🚧 ${utils_1.decorators.yellow(`Chain Spec file ${chainSpecFullPath} is TOO LARGE to customize (more than 2G).`)} 🚧`);
}
// add spec file to copy to all collators.
parachain.specPath = chainSpecFullPath;
}
// state and wasm files are only needed:
// IFF the relaychain is NOT RAW or
// IFF the relaychain is raw and addToGenesis is false for the parachain
const stateAndWasmAreNeeded = !(relayChainSpecIsRaw && parachain.addToGenesis);
// check if we need to create files
if (stateAndWasmAreNeeded &&
(parachain.genesisStateGenerator || parachain.genesisWasmGenerator)) {
const filesToCopyToNodes = [];
if (parachain.cumulusBased && chainSpecFullPath)
filesToCopyToNodes.push({
localFilePath: chainSpecFullPath,
remoteFilePath: `${client.remoteDir}/${chainSpecFileName}`,
});
const commands = [];
if (parachain.genesisStateGenerator) {
const subcommand = parachain.collators[0].substrateCliArgsVersion >= 3
? constants_1.DEFAULT_GENESIS_HEAD_GENERATE_SUBCOMMAND
: constants_1.DEFAULT_GENESIS_GENERATE_SUBCOMMAND;
let genesisStateGenerator = parachain.genesisStateGenerator
.replace("{{CLIENT_REMOTE_DIR}}", client.remoteDir)
.replace("{{GENESIS_GENERATE_SUBCOMMAND}}", subcommand);
// cumulus
if (parachain.cumulusBased) {
const chainSpecPathInNode = client.providerName === "native"
? chainSpecFullPath
: `${client.remoteDir}/${chainSpecFileName}`;
genesisStateGenerator = injectChainInCmd(genesisStateGenerator, chainSpecPathInNode);
}
if (client.providerName === "native") {
genesisStateGenerator = yield injectBasePathInCmd(client, parachain.id, genesisStateGenerator);
}
commands.push(`${genesisStateGenerator}-${parachain.id}`);
}
if (parachain.genesisWasmGenerator) {
let genesisWasmGenerator = parachain.genesisWasmGenerator.replace("{{CLIENT_REMOTE_DIR}}", client.remoteDir);
// cumulus
if (parachain.collators[0].zombieRole === sharedTypes_1.ZombieRole.CumulusCollator) {
const chainSpecPathInNode = client.providerName === "native"
? chainSpecFullPath
: `${client.remoteDir}/${chainSpecFileName}`;
genesisWasmGenerator = injectChainInCmd(genesisWasmGenerator, chainSpecPathInNode);
}
commands.push(`${genesisWasmGenerator}-${parachain.id}`);
}
// Native provider doesn't need to wait
if (client.providerName == "kubernetes")
commands.push(constants_1.K8S_WAIT_UNTIL_SCRIPT_SUFIX);
else if (client.providerName == "podman")
commands.push(constants_1.WAIT_UNTIL_SCRIPT_SUFIX);
const node = {
name: (0, configGenerator_1.getUniqueName)("temp-collator"),
validator: false,
invulnerable: false,
image: parachain.collators[0].image || constants_1.DEFAULT_COLLATOR_IMAGE,
fullCommand: commands.join(" && "),
chain: relayChainName,
bootnodes: [],
args: [],
env: [],
telemetryUrl: "",
overrides: [],
zombieRole: sharedTypes_1.ZombieRole.Temp,
p2pPort: yield (0, utils_1.getRandomPort)(),
wsPort: yield (0, utils_1.getRandomPort)(),
rpcPort: yield (0, utils_1.getRandomPort)(),
prometheusPort: yield (0, utils_1.getRandomPort)(),
};
const provider = providers_1.Providers.get(client.providerName);
const podDef = yield provider.genNodeDef(namespace, node);
const podName = podDef.metadata.name;
yield client.spawnFromDef(podDef, filesToCopyToNodes);
if (client.providerName === "kubernetes") {
debug("waiting for artifacts been created in pod");
yield client.waitLog(podName, podName, constants_1.NODE_CONTAINER_WAIT_LOG);
}
if (parachain.genesisStateGenerator) {
yield client.copyFileFromPod(podDef.metadata.name, `${client.remoteDir}/${GENESIS_STATE_FILENAME_WITH_ID}`, stateLocalFilePath);
}
if (parachain.genesisWasmGenerator) {
yield client.copyFileFromPod(podDef.metadata.name, `${client.remoteDir}/${GENESIS_WASM_FILENAME_WITH_ID}`, wasmLocalFilePath);
}
yield client.putLocalMagicFile(podName, podName);
}
if (parachain.genesisStatePath) {
fs_1.default.copyFileSync(parachain.genesisStatePath, stateLocalFilePath);
}
if (parachain.genesisWasmPath) {
fs_1.default.copyFileSync(parachain.genesisWasmPath, wasmLocalFilePath);
}
// add paths to para files
parachain.wasmPath = wasmLocalFilePath;
parachain.statePath = stateLocalFilePath;
return;
});
}
function getChainSpecCmdRaw(chainSpecCommand) {
// Default to the provided cmd, will work for custom generator.
let returnCmd = chainSpecCommand;
const parts = chainSpecCommand
.split(" ")
.filter((part) => part.length);
if (parts.includes("build-spec") && !parts.includes("--chain")) {
returnCmd = `${chainSpecCommand} --chain {{chainName}}`;
}
return returnCmd;
}
// Inject the chain (e.g. --chain <chain path>) before the output file or the
// shell redirection `>`.
function injectChainInCmd(cmd, chain) {
const parts = cmd.split(" ").filter(Boolean);
const l = parts.length;
const index = parts[l - 2] == ">" ? l - 2 : l - 1;
parts.splice(index, 0, `--chain ${chain}`);
return parts.join(" ");
}
// NOTE: This is only used in native provider since in k8s/podman the fs is always fresh
// Inject the base-path (e.g. --base-path <path> or -d <path>) IFF is not present
function injectBasePathInCmd(client, parachainId, cmd) {
return __awaiter(this, void 0, void 0, function* () {
const parts = cmd.split(" ").filter(Boolean);
// IFF is present don't modify the cmd
if (parts.includes("-d") || parts.includes("--base-path"))
return cmd;
// Check if the binary support the --base-path / -d flag
const helpCmd = `${parts[0]} ${parts[1]} --help`;
const helpText = (yield client.runCommand(["-c", helpCmd], { allowFail: true })).stdout;
if (!helpText.includes("--base-path"))
return cmd; // flag not supported
// Inject a tmp base-path to prevent the use of a pre-existing un-purged data directory.
// See https://github.com/paritytech/zombienet/issues/1519
const exportGenesisStateCustomPath = `${client.tmpDir}/export-genesis-state/${parachainId}`;
yield fs_1.default.promises.mkdir(exportGenesisStateCustomPath, {
recursive: true,
});
// inject just after the subcommand
debug(`Injecting tmp dir for ${parts[0]} ${parts[1]} cmd, paraId ${parachainId}`);
parts.splice(2, 0, `-d ${exportGenesisStateCustomPath}`);
return parts.join(" ");
});
}