diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 235fc4d40..10d04febb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -305,3 +305,34 @@ db-snapshot: retry: 2 tags: - zombienet-polkadot-integration-test + +zombienet-chain-spec-custom-modification: + stage: deploy + <<: *kubernetes-env + image: "paritypr/zombienet:${CI_COMMIT_SHORT_SHA}" + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + # needs: + # - job: publish-docker-pr + + variables: + GH_DIR: "https://github.com/paritytech/zombienet/tree/${CI_COMMIT_SHORT_SHA}/tests" + + before_script: + - echo "Zombienet Tests Custom Chain Spec Modification" + - echo "paritypr/zombienet:${CI_COMMIT_SHORT_SHA}" + - echo "${GH_DIR}" + - export DEBUG=zombie* + - export ZOMBIENET_INTEGRATION_TEST_IMAGE="docker.io/paritypr/polkadot-debug:master" + - export COL_IMAGE="docker.io/paritypr/colander:master" + + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --test="0014-chain-spec-modification.zndsl" + allow_failure: true + retry: 2 + tags: + - zombienet-polkadot-integration-test diff --git a/README.md b/README.md index a12c7d79c..bcac3b61c 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,27 @@ export COL_IMAGE=docker.io/paritypr/colander:master ./zombienet-macos spawn examples/0001-small-network.toml ``` +#### Custom commands to modify the resulting chain-specs + +If you want to customize the chain specification, plain or raw, that one of your chains will be launched with, beyond +what Zombienet provides by default, you can do so with `chain_spec_modifier_commands`. + +The `chain_spec_modifier_commands` option allows you to specify a list of CLI commands that will use, modify and return +the modified chain-spec before it is used to launch the network. These commands can modify the chain specification in any way you need. + +```toml +... +[[parachains]] +id = 100 +chain_spec_modifier_commands = [[ + "/path/to/your_custom_script.sh", + "{{'plain'|chainSpec}}" +]] +... +``` + +For a more in-depth example with a chain-querying tool `chainql`, you can visit the [examples](examples) directory. + ##### Teardown You can teardown the network (and cleanup the used resources) by terminating the process (`ctrl+c`). diff --git a/docs/src/network-definition-spec.md b/docs/src/network-definition-spec.md index efaf600dc..447ae8a77 100644 --- a/docs/src/network-definition-spec.md +++ b/docs/src/network-definition-spec.md @@ -34,6 +34,7 @@ The network config can be provided both in `json` or `toml` format and each sect - `remote_name`: string; - `default_resources`: (Object) **Only** available in `kubernetes`, represent the resources `limits`/`reservations` needed by the nodes by default. - `default_prometheus_prefix`: A parameter for customizing the metric's prefix. If parameter is placed in `relaychain` level, it will be "passed" to all `relaychain` nodes. Defaults to 'substrate'. +- `chain_spec_modifier_commands`: (Array of `commands`, optional) Set of commands and their arguments to modify the resulting chain spec before the chain is launched with it. `Commands` are themselves arrays of strings (each argument is a string). In the arguments, `{{'plain'|chainSpec}}` will be substituted for the plain spec path and run before the raw chain spec is generated, and `{{'raw'|chainSpec}}` will be substituted for the raw chain spec path. - `random_nominators_count`: (number, optional), if is set _and the stacking pallet is enabled_ zombienet will generate `x` nominators and will be injected in the genesis. - `max_nominations`: (number, default 24), the max allowed number of nominations by a nominator. This should match the value set in the runtime (e.g Kusama is 24 and Polkadot 16). - `nodes`: @@ -78,6 +79,7 @@ The network config can be provided both in `json` or `toml` format and each sect - `*id`: (Number) The id to assign to this parachain. Must be unique. - `add_to_genesis`: (Boolean, default true) flag to add parachain to genesis or register in runtime. - `cumulus_based`: (Boolean, default true) flag to use `cumulus` command generation. + - `chain_spec_modifier_commands`: (Array of `commands`, optional) Set of commands and their arguments to modify the resulting chain spec before the chain is launched with it. `Commands` are themselves arrays of strings (each argument is a string). In the arguments, `{{'plain'|chainSpec}}` will be substituted for the plain spec path and run before the raw chain spec is generated, and `{{'raw'|chainSpec}}` will be substituted for the raw chain spec path. - `genesis_wasm_path`: (String) Path to the wasm file to use. - `genesis_wasm_generator`: (String) Command to generate the wasm file. - `genesis_state_path`: (String) Path to the state file to use. diff --git a/examples/0005-chain-spec-mutation-with-chainql.toml b/examples/0005-chain-spec-mutation-with-chainql.toml new file mode 100644 index 000000000..51fe06c82 --- /dev/null +++ b/examples/0005-chain-spec-mutation-with-chainql.toml @@ -0,0 +1,31 @@ +# examples/0005-chain-spec-mutation-with-chainql.toml +[relaychain] +default_image = "docker.io/parity/polkadot:latest" +default_command = "polkadot" +default_args = [ "-lparachain=debug" ] + +chain = "rococo-local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + +[[parachains]] +id = 100 +# make sure to have chainql installed (`cargo install chainql`) +# https://github.com/UniqueNetwork/chainql +chain_spec_modifier_commands = [[ + "chainql", + "--tla-code=rawSpec=import '{{'raw'|chainSpec}}'", + "--tla-str=pullFrom=wss://rococo-rockmine-rpc.polkadot.io:443", + "--trace-format=explaining", + "chainqlCopyBalances.jsonnet", +]] + + [[parachains.collator_groups]] + count = 2 + name = "collator" diff --git a/examples/chainqlCopyBalances.jsonnet b/examples/chainqlCopyBalances.jsonnet new file mode 100644 index 000000000..55b005735 --- /dev/null +++ b/examples/chainqlCopyBalances.jsonnet @@ -0,0 +1,53 @@ +// Get a live chain's system balances on the URL provided with `pullFrom`, and +// insert them into a raw spec to launch a new chain with. +// +// ### Arguments +// - `rawSpec`: Path to the raw chain spec to modify +// - `pullFrom`: URL of the chain's WS port to get the data from +// +// ### Usage +// `chainql --tla-code=rawSpec="import '/path/to/parachain-spec-raw.json'" --tla-str=pullFrom="wss://some-parachain.node:443" chainqlCopyBalances.jsonnet` +// +// Make sure to to have `chainql` installed: `cargo install chainql` + +function(rawSpec, pullFrom) +// get the latest state of the blockchain +local sourceChainState = cql.chain(pullFrom).latest; + +local + // store all keys under the `Account` storage of the `System` pallet + accounts = sourceChainState.System.Account._preloadKeys, + // get the encoded naming of `pallet_balances::TotalIssuance` for future use + totalIssuanceKey = sourceChainState.Balances._encodeKey.TotalIssuance([]), +; + +// output the raw spec with the following changes +rawSpec { + genesis+: { + raw+: { + // add the following entries to the `top` section + top+: + { + // encode key and value of every account under `system.account` and add them to the chain spec + [sourceChainState.System._encodeKey.Account([key])]: + sourceChainState.System._encodeValue.Account(accounts[key]) + for key in std.objectFields(accounts) + } + { + // add to the local, already-existing total issuance the issuance of all incoming accounts. + // NOTE: we do not take into consideration for total issuance's funds potential overlap with the testnet's present accounts. + [totalIssuanceKey]: sourceChainState.Balances._encodeValue.TotalIssuance( + // decode the chain-spec's already existing totalIssuance + sourceChainState.Balances._decodeValue.TotalIssuance(super[totalIssuanceKey]) + // iterate over and sum up the total issuance of the incoming accounts + + std.foldl( + function(issuance, acc) + issuance + acc.data.free + acc.data.reserved + , + std.objectValues(accounts), + std.bigint('0'), + ) + ) + }, + }, + }, +} \ No newline at end of file diff --git a/javascript/packages/orchestrator/src/chainSpec.ts b/javascript/packages/orchestrator/src/chainSpec.ts index be989919e..c4b30030f 100644 --- a/javascript/packages/orchestrator/src/chainSpec.ts +++ b/javascript/packages/orchestrator/src/chainSpec.ts @@ -6,10 +6,21 @@ import { getRandom, readDataFile, } from "@zombienet/utils"; +import { ChildProcessWithoutNullStreams, spawn } from "child_process"; import crypto from "crypto"; import fs from "fs"; +import { + PLAIN_CHAIN_SPEC_IN_CMD_PATTERN, + RAW_CHAIN_SPEC_IN_CMD_PATTERN, +} from "./constants"; import { generateKeyFromSeed } from "./keys"; -import { ChainSpec, ComputedNetwork, HrmpChannelsConfig, Node } from "./types"; +import { + ChainSpec, + Command, + ComputedNetwork, + HrmpChannelsConfig, + Node, +} from "./types"; const JSONbig = require("json-bigint")({ useNativeBigInt: true }); const debug = require("debug")("zombie::chain-spec"); @@ -18,6 +29,15 @@ const JSONStream = require("JSONStream"); // track 1st staking as default; let stakingBond: number | undefined; +const processes: { [key: string]: ChildProcessWithoutNullStreams } = {}; + +// kill any runnning processes related to non-node chain spec processing +export async function destroyChainSpecProcesses() { + for (const key of Object.keys(processes)) { + processes[key].kill(); + } +} + export type KeyType = "session" | "aura" | "grandpa"; export type GenesisNodeKey = [string, string, { [key: string]: string }]; @@ -664,6 +684,75 @@ export async function getChainIdFromSpec(specPath: string): Promise { }); } +export async function runCommandWithChainSpec( + chainSpecFullPath: string, + commandWithArgs: Command, + workingDirectory: string | URL, +) { + const chainSpecSubstitutePattern = new RegExp( + RAW_CHAIN_SPEC_IN_CMD_PATTERN?.source + + "|" + + PLAIN_CHAIN_SPEC_IN_CMD_PATTERN?.source, + "gi", + ); + + const substitutedCommandArgs = commandWithArgs.map( + (arg) => `${arg.replaceAll(chainSpecSubstitutePattern, chainSpecFullPath)}`, + ); + const chainSpecModifiedPath = chainSpecFullPath.replace( + ".json", + "-modified.json", + ); + + new CreateLogTable({ colWidths: [30, 90] }).pushToPrint([ + [ + decorators.green("🧪 Mutating chain spec"), + decorators.white(substitutedCommandArgs.join(" ")), + ], + ]); + + try { + await new Promise(function (resolve, reject) { + if (processes["mutator"]) { + processes["mutator"].kill(); + } + + // spawn the chain spec mutator thread with the command and arguments + processes["mutator"] = spawn( + substitutedCommandArgs[0], + substitutedCommandArgs.slice(1), + { cwd: workingDirectory }, + ); + // flush the modified spec to a different file and then copy it back into the original path + const spec = fs.createWriteStream(chainSpecModifiedPath); + + // `pipe` since it deals with flushing and we need to guarantee that the data is flushed + // before we resolve the promise. + processes["mutator"].stdout.pipe(spec); + + processes["mutator"].stderr.pipe(process.stderr); + + processes["mutator"].on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Process returned error code ${code}!`)); + } + }); + + processes["mutator"].on("error", (err) => { + reject(err); + }); + }); + + // copy the modified file back into the original path after the mutation has completed + fs.copyFileSync(chainSpecModifiedPath, chainSpecFullPath); + } catch (e: any) { + console.error(`\n${decorators.red("Failed to mutate chain spec!")}`); + throw e; + } +} + export async function customizePlainRelayChain( specPath: string, networkSpec: ComputedNetwork, @@ -716,6 +805,11 @@ export async function customizePlainRelayChain( if (networkSpec.hrmp_channels) { await addHrmpChannelsToGenesis(specPath, networkSpec.hrmp_channels); } + + // modify the plain chain spec with any custom commands + for (const cmd of networkSpec.relaychain.chainSpecModifierCommands) { + await runCommandWithChainSpec(specPath, cmd, networkSpec.configBasePath); + } } catch (err) { console.log( `\n ${decorators.red("Unexpected error: ")} \t ${decorators.bright( diff --git a/javascript/packages/orchestrator/src/configGenerator.ts b/javascript/packages/orchestrator/src/configGenerator.ts index 5cbf92744..fa60f2eee 100644 --- a/javascript/packages/orchestrator/src/configGenerator.ts +++ b/javascript/packages/orchestrator/src/configGenerator.ts @@ -25,6 +25,8 @@ import { DEFAULT_WASM_GENERATE_SUBCOMMAND, GENESIS_STATE_FILENAME, GENESIS_WASM_FILENAME, + PLAIN_CHAIN_SPEC_IN_CMD_PATTERN, + RAW_CHAIN_SPEC_IN_CMD_PATTERN, UNDYING_COLLATOR_BIN, ZOMBIE_WRAPPER, } from "./constants"; @@ -119,6 +121,8 @@ export async function generateNetworkSpec( defaultImage: config.relaychain.default_image || DEFAULT_IMAGE, defaultCommand: config.relaychain.default_command || DEFAULT_COMMAND, defaultArgs: config.relaychain.default_args || [], + chainSpecModifierCommands: [], + rawChainSpecModifierCommands: [], randomNominatorsCount: config.relaychain?.random_nominators_count || 0, maxNominations: config.relaychain?.max_nominations || DEFAULT_MAX_NOMINATIONS, @@ -182,6 +186,35 @@ export async function generateNetworkSpec( ).replace("{{DEFAULT_COMMAND}}", networkSpec.relaychain.defaultCommand); } + for (const cmd of config.relaychain.chain_spec_modifier_commands || []) { + const cmdHasRawSpec = cmd.some((arg) => + RAW_CHAIN_SPEC_IN_CMD_PATTERN.test(arg), + ); + const cmdHasPlainSpec = cmd.some((arg) => + PLAIN_CHAIN_SPEC_IN_CMD_PATTERN.test(arg), + ); + + if (cmdHasRawSpec && cmdHasPlainSpec) { + console.log( + decorators.yellow( + `Chain spec modifier command references both raw and plain chain specs! Only the raw chain spec will be modified.\n\t${cmd}`, + ), + ); + } + + if (cmdHasRawSpec) { + networkSpec.relaychain.rawChainSpecModifierCommands.push(cmd); + } else if (cmdHasPlainSpec) { + networkSpec.relaychain.chainSpecModifierCommands.push(cmd); + } else { + console.log( + decorators.yellow( + `Chain spec modifier command does not attempt to reference a chain spec path! It will not be executed.\n\t${cmd}`, + ), + ); + } + } + const relayChainBootnodes: string[] = []; for (const node of config.relaychain.nodes || []) { const nodeSetup = await getNodeFromConfig( @@ -373,6 +406,8 @@ export async function generateNetworkSpec( name: getUniqueName(parachain.id.toString()), para, cumulusBased: isCumulusBased, + chainSpecModifierCommands: [], + rawChainSpecModifierCommands: [], addToGenesis: parachain.add_to_genesis === undefined ? true @@ -405,6 +440,35 @@ export async function generateNetworkSpec( } } + for (const cmd of parachain.chain_spec_modifier_commands || []) { + const cmdHasRawSpec = cmd.some((arg) => + RAW_CHAIN_SPEC_IN_CMD_PATTERN.test(arg), + ); + const cmdHasPlainSpec = cmd.some((arg) => + PLAIN_CHAIN_SPEC_IN_CMD_PATTERN.test(arg), + ); + + if (cmdHasRawSpec && cmdHasPlainSpec) { + console.log( + decorators.yellow( + `Chain spec modifier command references both raw and plain chain specs! Only the raw chain spec will be modified.\n\t${cmd}`, + ), + ); + } + + if (cmdHasRawSpec) { + parachainSetup.rawChainSpecModifierCommands.push(cmd); + } else if (cmdHasPlainSpec) { + parachainSetup.chainSpecModifierCommands.push(cmd); + } else { + console.log( + decorators.yellow( + `Chain spec modifier command does not attempt to reference a chain spec path! It will not be executed.\n\t${cmd}`, + ), + ); + } + } + parachainSetup = { ...parachainSetup, ...(parachain.balance ? { balance: parachain.balance } : {}), diff --git a/javascript/packages/orchestrator/src/constants.ts b/javascript/packages/orchestrator/src/constants.ts index 3636db2ef..3006fc9f0 100644 --- a/javascript/packages/orchestrator/src/constants.ts +++ b/javascript/packages/orchestrator/src/constants.ts @@ -52,6 +52,9 @@ const FINISH_MAGIC_FILE = "/tmp/finished.txt"; const GENESIS_STATE_FILENAME = "genesis-state"; const GENESIS_WASM_FILENAME = "genesis-wasm"; +const RAW_CHAIN_SPEC_IN_CMD_PATTERN = new RegExp(/{{CHAIN_SPEC:RAW}}/gi); +const PLAIN_CHAIN_SPEC_IN_CMD_PATTERN = new RegExp(/{{CHAIN_SPEC:PLAIN}}/gi); + const TMP_DONE = "echo done > /tmp/zombie-tmp-done"; const TRANSFER_CONTAINER_WAIT_LOG = "waiting for tar to finish"; const NODE_CONTAINER_WAIT_LOG = "waiting for copy files to finish"; @@ -156,7 +159,9 @@ export { METRICS_URI_PATTERN, NODE_CONTAINER_WAIT_LOG, P2P_PORT, + PLAIN_CHAIN_SPEC_IN_CMD_PATTERN, PROMETHEUS_PORT, + RAW_CHAIN_SPEC_IN_CMD_PATTERN, REGULAR_BIN_PATH, RPC_HTTP_PORT, RPC_WS_PORT, diff --git a/javascript/packages/orchestrator/src/network.ts b/javascript/packages/orchestrator/src/network.ts index 365e72170..14df063de 100644 --- a/javascript/packages/orchestrator/src/network.ts +++ b/javascript/packages/orchestrator/src/network.ts @@ -4,6 +4,7 @@ import { decorators, } from "@zombienet/utils"; import fs from "fs"; +import { destroyChainSpecProcesses } from "./chainSpec"; import { BAKCCHANNEL_POD_NAME, BAKCCHANNEL_PORT, @@ -154,8 +155,10 @@ export class Network { async stop() { // Cleanup all api instances - for (const node of Object.values(this.nodesByName)) + for (const node of Object.values(this.nodesByName)) { node.apiInstance?.disconnect(); + } + await destroyChainSpecProcesses(); await this.client.destroyNamespace(); } diff --git a/javascript/packages/orchestrator/src/orchestrator.ts b/javascript/packages/orchestrator/src/orchestrator.ts index ac94e7181..e34e50b1e 100644 --- a/javascript/packages/orchestrator/src/orchestrator.ts +++ b/javascript/packages/orchestrator/src/orchestrator.ts @@ -21,6 +21,7 @@ import { addParachainToGenesis, customizePlainRelayChain, readAndParseChainSpec, + runCommandWithChainSpec, } from "./chainSpec"; import { generateBootnodeSpec, @@ -257,6 +258,7 @@ export async function start( namespace, tmpDir.path, parachainFilesPath, + networkSpec.configBasePath, chainName, parachain, relayChainSpecIsRaw, @@ -350,6 +352,15 @@ export async function start( networkSpec.relaychain.nodes.unshift(bootnodeSpec); } + // modify the raw chain spec with any custom commands + for (const cmd of networkSpec.relaychain.rawChainSpecModifierCommands) { + await runCommandWithChainSpec( + chainSpecFullPath, + cmd, + networkSpec.configBasePath, + ); + } + const monitorIsAvailable = await client.isPodMonitorAvailable(); let jaegerUrl: string | undefined = undefined; if (networkSpec.settings.enable_tracing) { diff --git a/javascript/packages/orchestrator/src/paras.ts b/javascript/packages/orchestrator/src/paras.ts index 5f56dd676..f71ea1d9a 100644 --- a/javascript/packages/orchestrator/src/paras.ts +++ b/javascript/packages/orchestrator/src/paras.ts @@ -1,6 +1,6 @@ import { decorators, getRandomPort } from "@zombienet/utils"; import fs from "fs"; -import chainSpecFns, { isRawSpec } from "./chainSpec"; +import chainSpecFns, { isRawSpec, runCommandWithChainSpec } from "./chainSpec"; import { getUniqueName } from "./configGenerator"; import { DEFAULT_COLLATOR_IMAGE, @@ -20,6 +20,7 @@ export async function generateParachainFiles( namespace: string, tmpDir: string, parachainFilesPath: string, + configBasePath: string | URL, relayChainName: string, parachain: Parachain, relayChainSpecIsRaw: boolean, @@ -141,6 +142,15 @@ export async function generateParachainFiles( } } + // modify the plain chain spec with any custom commands + for (const cmd of parachain.chainSpecModifierCommands) { + await runCommandWithChainSpec( + chainSpecFullPathPlain, + cmd, + configBasePath, + ); + } + debug("creating chain spec raw"); // ensure needed file if (parachain.chain) @@ -186,6 +196,11 @@ export async function generateParachainFiles( // add spec file to copy to all collators. parachain.specPath = chainSpecFullPath; + + // modify the raw chain spec with any custom commands + for (const cmd of parachain.rawChainSpecModifierCommands) { + await runCommandWithChainSpec(chainSpecFullPath, cmd, configBasePath); + } } // state and wasm files are only needed: diff --git a/javascript/packages/orchestrator/src/types.ts b/javascript/packages/orchestrator/src/types.ts index 06287eebe..7c6185c7b 100644 --- a/javascript/packages/orchestrator/src/types.ts +++ b/javascript/packages/orchestrator/src/types.ts @@ -51,6 +51,7 @@ export interface RelayChainConfig { chain_spec_command?: string; default_args?: string[]; default_overrides?: Override[]; + chain_spec_modifier_commands?: Command[]; random_nominators_count?: number; max_nominations?: number; nodes?: NodeConfig[]; @@ -112,6 +113,7 @@ export interface ParachainConfig { cumulus_based?: boolean; bootnodes?: string[]; prometheus_prefix?: string; + chain_spec_modifier_commands?: Command[]; // backward compatibility collator?: NodeConfig; collators?: NodeConfig[]; @@ -138,6 +140,8 @@ export interface ComputedNetwork { chain: string; chainSpecPath?: string; chainSpecCommand?: string; + chainSpecModifierCommands: Command[]; + rawChainSpecModifierCommands: Command[]; randomNominatorsCount: number; maxNominations: number; nodes: Node[]; @@ -227,6 +231,8 @@ export interface Parachain { balance?: number; collators: Node[]; genesis?: JSON | ObjectJSON; + chainSpecModifierCommands: Command[]; + rawChainSpecModifierCommands: Command[]; } export interface envVars { @@ -234,6 +240,8 @@ export interface envVars { value: string; } +export type Command = string[]; + export interface ChainSpec { name: string; id: string; diff --git a/javascript/packages/utils/src/fs.ts b/javascript/packages/utils/src/fs.ts index e0128f550..3a6d14276 100644 --- a/javascript/packages/utils/src/fs.ts +++ b/javascript/packages/utils/src/fs.ts @@ -163,6 +163,10 @@ export function readNetworkConfig(filepath: string): LaunchConfig { return `{{ZOMBIE:${nodeName}:${key}}}`; }); + env.addFilter("chainSpec", function (chainSpecType: string) { + return `{{CHAIN_SPEC:${chainSpecType.toUpperCase()}}}`; + }); + const temmplateContent = fs.readFileSync(filepath).toString(); const content = env.renderString(temmplateContent, process.env); diff --git a/javascript/packages/utils/src/types.ts b/javascript/packages/utils/src/types.ts index bc25093e8..791061d8f 100644 --- a/javascript/packages/utils/src/types.ts +++ b/javascript/packages/utils/src/types.ts @@ -42,6 +42,7 @@ export interface RelayChainConfig { chain_spec_command?: string; default_args?: string[]; default_overrides?: Override[]; + chain_spec_modifier_commands?: Command[]; random_nominators_count?: number; max_nominations?: number; nodes?: NodeConfig[]; @@ -94,6 +95,7 @@ export interface ParachainConfig { chain_spec_path?: string; cumulus_based?: boolean; bootnodes?: string[]; + chain_spec_modifier_commands?: Command[]; // backward compatibility collator?: NodeConfig; collators?: NodeConfig[]; @@ -113,6 +115,8 @@ export interface envVars { value: string; } +export type Command = string[]; + // Utils export interface GlobalVolume { name: string; diff --git a/tests/0014-chain-spec-modification.toml b/tests/0014-chain-spec-modification.toml new file mode 100644 index 000000000..1052dc61c --- /dev/null +++ b/tests/0014-chain-spec-modification.toml @@ -0,0 +1,18 @@ +[relaychain] +default_image = "docker.io/parity/polkadot:latest" +default_command = "polkadot" +default_args = [ "-lparachain=debug" ] +chain = "rococo-local" + +chain_spec_modifier_commands = [[ + "./change-name-in-chain-spec.sh", + "{{'plain'|chainSpec}}", +]] + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/tests/0014-chain-spec-modification.zndsl b/tests/0014-chain-spec-modification.zndsl new file mode 100644 index 000000000..5d68f35c5 --- /dev/null +++ b/tests/0014-chain-spec-modification.zndsl @@ -0,0 +1,10 @@ +Description: Chain Spec Modification +Network: ./0014-chain-spec-modification.toml +Creds: config + + +alice: is up +bob: is up + +alice: log line matches "Tentset Lacol Ococor" +alice: log line matches "Imported #[0-9]+" within 10 seconds diff --git a/tests/change-name-in-chain-spec.sh b/tests/change-name-in-chain-spec.sh new file mode 100755 index 000000000..1b79e6426 --- /dev/null +++ b/tests/change-name-in-chain-spec.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# The first argument sent to the script must be the path of the chain spec +chain_spec_path=$1 +# Assume that the name +old_name="Rococo Local Testnet" +# Set the new name for the network +new_name="Tentset Lacol Ococor" + +input=$(cat $chain_spec_path) + +# Replace the name field in the JSON file +echo "$input" | sed "s/\"name\": \"$old_name\"/\"name\": \"$new_name\"/"