Skip to content

Commit

Permalink
initial impl of chain-upgrade (#51)
Browse files Browse the repository at this point in the history
* initial impl of chain-upgrade

* fmt

* code for dummy upgrade

* add test for dummy chain upgrade

* fix run script

* typo

* add script to run local tests

* clean and fix ci

* debug in ci

* add tests to image

* typo
  • Loading branch information
pepoviola authored Jan 4, 2022
1 parent f0f9e1e commit fa5d5e5
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 1 deletion.
31 changes: 31 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,34 @@ zombienet-tests-integration:
retry: 2
tags:
- zombienet-polkadot-integration-test

zombienet-dummy-chain-upgrade:
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/feat-chain-upgrade-command/tests'

before_script:
- echo "Zombie-net Tests Config"
- echo "paritypr/zombienet:${CI_COMMIT_SHORT_SHA}"
- echo "${GH_DIR}"
- export DEBUG=zombie*
- export ZOMBIENET_INTEGRATION_TEST_IMAGE="docker.io/paritypr/synth-wave:master"
- export COL_IMAGE="docker.io/paritypr/colander:master"

script:
- /home/nonroot/zombie-net/scripts/run-test-local-env-manager.sh
--test="0003-parachains-upgrade-smoke-test.feature"
allow_failure: true
retry: 2
tags:
- zombienet-polkadot-integration-test
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"debug": "^4.3.2",
"execa": "^5.1.1",
"mocha": "^9.1.2",
"napi-maybe-compressed-blob": "0.0.2",
"tmp-promise": "^3.0.2",
"toml": "^3.0.0",
"yaml": "^2.0.0-9"
Expand Down
1 change: 1 addition & 0 deletions scripts/docker/zombienet_injected.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ WORKDIR /home/nonroot/zombie-net
COPY ./artifacts/dist ./dist
COPY static-configs ./static-configs
COPY scripts ./scripts
COPY tests ./tests
COPY artifacts/package* ./
RUN npm install --production
RUN chown -R nonroot. /home/nonroot
Expand Down
5 changes: 5 additions & 0 deletions scripts/run-test-env-manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,15 @@ function run_test {
set -x
set +e
if [[ ! -z $TEST_TO_RUN ]]; then
TEST_FOUND=0
for i in $(find ${OUTPUT_DIR} -name "${TEST_TO_RUN}"| head -1); do
TEST_FOUND=1
zombie test $i
EXIT_STATUS=$?
done;
if [[ $TEST_FOUND -lt 1 ]]; then
EXIT_STATUS=1
fi;
else
for i in $(find ${OUTPUT_DIR} -name *.feature | sort); do
echo "running test: ${i}"
Expand Down
161 changes: 161 additions & 0 deletions scripts/run-test-local-env-manager.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/bin/bash

# Based on https://gitlab.parity.io/parity/simnet/-/blob/master/scripts/run-test-environment-manager-v2.sh

set -eou pipefail


function usage {
cat << EOF
DEPENDENCY 1: gcloud
https://cloud.google.com/sdk/docs/install
DEPENDENCY 2: kubectl
gcloud components install kubectl
Usage: ${SCRIPT_NAME} OPTION
OPTION
-t, --test OPTIONAL Test file to run
If omitted "all" test in the tests directory will be used.
-h, --help OPTIONAL Print this help message
-o, --output-dir OPTIONAL
Path to dir where to save contens of --github-remote-dir
Defaults to ${SCRIPT_PATH}
specified, it will be ifered from there.
EXAMPLES
Run tests
${SCRIPT_NAME} -g https://github.com/paritytech/polkadot/tree/master/zombienet_tests
EOF
}

function main {
# Main entry point for the script
set_defaults_for_globals
parse_args "$@"
create_isolated_dir
copy_to_isolated
run_test
log INFO "Exit status is ${EXIT_STATUS}"
exit "${EXIT_STATUS}"
}

function create_isolated_dir {
TS=$(date +%s)
ISOLATED=${OUTPUT_DIR}/${TS}
mkdir -p ${ISOLATED}
OUTPUT_DIR="${ISOLATED}"
}

function set_defaults_for_globals {
# DEFAULT VALUES for variables used for testing different projects
SCRIPT_NAME="$0"
SCRIPT_PATH=$(dirname "$0") # relative
SCRIPT_PATH=$(cd "${SCRIPT_PATH}" && pwd) # absolutized and normalized

export GOOGLE_CREDENTIALS="/etc/zombie-net/sa-zombie.json"

cd "${SCRIPT_PATH}"

EXIT_STATUS=0
GH_REMOTE_DIR=""
TEST_TO_RUN=""


LAUNCH_ARGUMENTS=""
USE_LOCAL_TESTS=false
OUTPUT_DIR="${SCRIPT_PATH}"
}

function parse_args {
function needs_arg {
if [ -z "${OPTARG}" ]; then
log DIE "No arg for --${OPT} option"
fi
}

function check_args {
if [[ -n "${GH_REMOTE_DIR}" &&
! "${GH_REMOTE_DIR}" =~ https:\/\/github.com\/ ]] ; then
log DIE "Not a github URL"
fi
}

# shellcheck disable=SC2214
while getopts i:t:g:h:uo:-: OPT; do
# support long options: https://stackoverflow.com/a/28466267/519360
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
case "$OPT" in
t | test) needs_arg ; TEST_TO_RUN="${OPTARG}" ;;
g | github-remote-dir) needs_arg ; GH_REMOTE_DIR="${OPTARG}" ;;
h | help ) usage ; exit 0 ;;
o | output-dir) needs_arg ; OUTPUT_DIR="${OPTARG}" ;;
??* ) log DIE "Illegal option --${OPT}" ;;
? ) exit 2 ;;
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
check_args
}

function copy_to_isolated {
cd "${SCRIPT_PATH}"
echo $(pwd)
echo $(ls)
echo $(ls ..)
cp -r ../tests/* "${OUTPUT_DIR}"
}
function run_test {
# RUN_IN_CONTAINER is env var that is set in the dockerfile
if [[ -v RUN_IN_CONTAINER ]]; then
gcloud auth activate-service-account --key-file "${GOOGLE_CREDENTIALS}"
gcloud container clusters get-credentials parity-zombienet --zone europe-west3-b --project parity-zombienet
fi
cd "${OUTPUT_DIR}"
set -x
set +e
if [[ ! -z $TEST_TO_RUN ]]; then
TEST_FOUND=0
for i in $(find ${OUTPUT_DIR} -name "${TEST_TO_RUN}"| head -1); do
TEST_FOUND=1
zombie test $i
EXIT_STATUS=$?
done;
if [[ $TEST_FOUND -lt 1 ]]; then
EXIT_STATUS=1
fi;
else
for i in $(find ${OUTPUT_DIR} -name *.feature | sort); do
echo "running test: ${i}"
zombie test $i
TEST_EXIT_STATUS=$?
EXIT_STATUS=$((EXIT_STATUS+TEST_EXIT_STATUS))
done;
fi

set +x
set -e
}

function log {
local lvl msg fmt
lvl=$1 msg=$2
fmt='+%Y-%m-%d %H:%M:%S'
lg_date=$(date "${fmt}")
if [[ "${lvl}" = "DIE" ]] ; then
lvl="ERROR"
echo -e "\n${lg_date} - ${lvl} - ${msg}"
exit 1
else
echo -e "\n${lg_date} - ${lvl} - ${msg}"
fi
}

main "$@"
71 changes: 71 additions & 0 deletions src/jsapi-helpers/chain-upgrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ApiPromise, Keyring } from "@polkadot/api";
import { withTypeString } from "@polkadot/types";
import { cryptoWaitReady } from "@polkadot/util-crypto";
import { readFileSync, promises as fsPromises } from "fs";

import { compress, decompress } from "napi-maybe-compressed-blob";

export async function chainUpgrade(api: ApiPromise, wasmFilePath: string): Promise<void> {
// The filename of the runtime/PVF we want to upgrade to. Usually a file
// with `.compact.compressed.wasm` extension.
console.log(`upgrading chain with file: ${wasmFilePath}`);

let code = readFileSync(wasmFilePath).toString("hex");
await performChainUpgrade(api, code);
}

export async function chainDummyUpgrade(api: ApiPromise): Promise<void> {
const code: any = await api.rpc.state.getStorage(":code");
const codeHex = code.toString().slice(2)
const codeBuf = Buffer.from(hexToBytes(codeHex));
const decompressed = decompress(codeBuf);

// add dummy
// echo -n -e "\x00\x07\x05\x64\x75\x6D\x6D\x79\x0A"
const dummyBuf = [0x00, 0x07, 0x05, 0x64, 0x75, 0x6D, 0x6D, 0x79, 0x0A];
const withDummyCode = Buffer.concat([decompressed, Buffer.from(dummyBuf)]);

// compress again
const compressed = compress(withDummyCode);

// perform upgrade
await performChainUpgrade(api, compressed.toString("hex"));
}


async function performChainUpgrade(api: ApiPromise, code: string) {
await cryptoWaitReady()

const keyring = new Keyring({ type: "sr25519" });
const alice = keyring.addFromUri("//Alice");

await new Promise<void>(async (resolve, reject) => {
const unsub = await api.tx.sudo
.sudoUncheckedWeight(api.tx.system.setCodeWithoutChecks(`0x${code}`), 1)
.signAndSend(alice, (result) => {
console.log(`Current status is ${result.status}`);
if (result.status.isInBlock) {
console.log(
`Transaction included at blockHash ${result.status.asInBlock}`
);
} else if (result.status.isFinalized) {
console.log(
`Transaction finalized at blockHash ${result.status.asFinalized}`
);
unsub();
return resolve();
} else if (result.isError) {
console.log(`Transaction Error`);
unsub();
return reject();
}
});
});
}

/// Internal
function hexToBytes(hex: any) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
15 changes: 15 additions & 0 deletions src/jsapi-helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiPromise, WsProvider } from "@polkadot/api";
import { chainUpgrade, chainDummyUpgrade } from "./chain-upgrade";

async function connect(apiUrl: string, types: any): Promise<ApiPromise> {
const provider = new WsProvider(apiUrl)
const api = new ApiPromise({ provider, types })
await api.isReady
return api
}

export {
connect,
chainUpgrade,
chainDummyUpgrade
}
Loading

0 comments on commit fa5d5e5

Please sign in to comment.