Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
40 changes: 40 additions & 0 deletions .github/workflows/simulate-runtime-upgrade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Simulate runtime upgrade

on:
release:
types: [released]

workflow_dispatch:
inputs:
release_tag:
description: runtime.wasm release_tag
required: true

env:
RELEASE_TAG: ${{ github.event.inputs.release_tag || github.event.release.tag_name }}

jobs:
simulate-runtime-upgrade:
runs-on: ubuntu-latest
strategy:
matrix:
chain:
# litmus is not supported, as sudo was removed
# TODO: add runtime upgrade via governance
- litentry
- rococo
steps:
- name: Checkout codes on ${{ github.ref }}
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Fork ${{ matrix.chain }} and launch parachain
timeout-minutes: 10
run: |
./scripts/fork-parachain-and-launch.sh ${{ matrix.chain }}

- name: Test runtime upgrade
timeout-minutes: 10
run: |
./scripts/runtime-upgrade.sh https://github.com/litentry/litentry-parachain/releases/download/${{ env.RELEASE_TAG }}/${{ matrix.chain }}-parachain-runtime.compact.compressed.wasm
75 changes: 36 additions & 39 deletions scripts/fork-parachain-and-launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ set -eo pipefail
ROOTDIR=$(git rev-parse --show-toplevel)
TMPDIR=$(mktemp -d /tmp/XXXXXX)

cleanup() {
echo "removing $1 ..."
rm -rf "$1"
exit
}

trap 'cleanup $TMPDIR' INT TERM EXIT

FORK_OFF_SUBSTRATE_REPO="https://github.com/litentry/fork-off-substrate.git"

function print_divider() {
Expand All @@ -18,19 +26,18 @@ function print_divider() {

function usage() {
print_divider
echo "Usage: $0 [http-rpc-endpoint] [orig-chain] [fork-chain] [binary]"
echo
echo "the http-rpc-endpoint has to be a reachabale HTTP-RPC URL (do not mix it up with ws port)"
echo
echo "default:"
echo "http-rpc-endpoint: http://localhost:9933"
echo "orig-chain: litmus"
echo "fork-chain: litmus-dev"
echo "binary: the binary copied from litentry/litentry-parachain:latest"
echo "Usage: $0 [chain] [ws-rpc-endpoint] [binary]"
echo
echo "chain: rococo|litmus|litentry"
echo " default: rococo"
echo "ws-rpc-endpoint: the ws rpc endpoint of the parachain"
echo " default: litentry-rococo's rpc endpoint"
echo "binary: path to the litentry parachain binary"
echo " default: the binary copied from litentry/litentry-parachain:latest"
print_divider
}

[ $# -gt 4 ] && (usage; exit 1)
[ $# -gt 3 ] && (usage; exit 1)

case "$1" in
help|-h|--help)
Expand All @@ -41,16 +48,19 @@ case "$1" in
;;
esac

ENDPOINT="${1:-http://localhost:9933}"
ORIG_CHAIN=${2:-litmus}
FORK_CHAIN=${3:-litmus-dev}
CHAIN_TYPE=
ORIG_CHAIN=${1:-rococo}
FORK_CHAIN=${ORIG_CHAIN}-dev

case "$FORK_CHAIN" in
litmus*)
CHAIN_TYPE=litmus ;;
litentry*)
CHAIN_TYPE=litentry ;;
case "$ORIG_CHAIN" in
rococo)
ENDPOINT="${2:-wss://rpc.rococo-parachain-sg.litentry.io}"
;;
litmus)
ENDPOINT="${2:-wss://rpc.litmus-parachain.litentry.io}"
;;
litentry)
ENDPOINT="${2:-wss://rpc.litentry-parachain.litentry.io}"
;;
*)
echo "unsupported chain type"
exit 1 ;;
Expand All @@ -65,30 +75,17 @@ npm i
mkdir data && cd data

# copy the binary
if [ -z "$4" ]; then
docker cp $(docker create --rm litentry/litentry-parachain:latest):/usr/local/bin/litentry-collator binary
if [ -z "$3" ]; then
docker cp "$(docker create --rm litentry/litentry-parachain:latest):/usr/local/bin/litentry-collator" binary
else
cp "$4" binary
cp -f "$3" binary
fi
chmod a+x binary

# pop up a warning if on non-CI host
if [ $(hostname) != "ubuntu-16gb-CI" ]; then
echo "WARNING: it seems you are not on the CI host"
echo " please make sure the given HTTP-RPC endpoint accessible"
else
# open the ssh port forwarding for a short time
ssh -f -L 9900:localhost:9933 litmus-sg-rpc0 sleep 120
fi

# retrieve the live wasm
curl -s -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getStorage", "params": [ "0x3a636f6465" ]}' "$ENDPOINT" | \
jq .result | sed 's/"//g;s/^0x//' | xxd -r -p > runtime.wasm

# write .env file
cd ..
cat << EOF > .env
HTTP_RPC_ENDPOINT=$ENDPOINT
WS_RPC_ENDPOINT=$ENDPOINT
ALICE=1
ORIG_CHAIN=$ORIG_CHAIN
FORK_CHAIN=$FORK_CHAIN
Expand All @@ -101,10 +98,10 @@ if [ ! -f data/fork.json ]; then
exit 2
fi

FORK_JSON_PATH="$(pwd)/data/fork.json"
cp -f data/fork.json "$ROOTDIR/docker/"

cd "$ROOTDIR"
sed -i.bak "s;$FORK_CHAIN;$FORK_JSON_PATH;" docker/$CHAIN_TYPE-parachain-launch-config.yml
sed -i.bak "s;$FORK_CHAIN;fork.json;" "docker/$ORIG_CHAIN-parachain-launch-config.yml"

# start the network
make launch-docker-$CHAIN_TYPE
make "launch-docker-$ORIG_CHAIN"
2 changes: 1 addition & 1 deletion scripts/launch-local-binary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ if [ -z "$POLKADOT_BIN" ]; then
# TODO: find a way to get stable download link
# https://api.github.com/repos/paritytech/polkadot/releases/latest is not reliable as
# polkadot could publish release which has no binary
url="https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot"
url="https://github.com/paritytech/polkadot/releases/download/v0.9.32/polkadot"
POLKADOT_BIN="$TMPDIR/polkadot"
wget -O "$POLKADOT_BIN" -q "$url"
chmod a+x "$POLKADOT_BIN"
Expand Down
62 changes: 62 additions & 0 deletions scripts/runtime-upgrade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash
# set -eo we don't allow any command failed in this script.
set -eo pipefail

ROOTDIR=$(git rev-parse --show-toplevel)

# the script is used to simulate runtime upgrade, see:
# https://github.com/litentry/litentry-parachain/issues/378

# The latest state of the blockchain is scraped and used to bootstrap a chain locally via fork-off-substrate,
# see ./scripts/fork-parachain-and-launch.sh
#
# After that, this script:
# 1. get the runtime wasm
# 2. do runtime upgrade using wasm from step 1
# 3. verify if the runtime upgrade is successful

output_wasm=/tmp/runtime.wasm

function usage() {
echo
echo "Usage: $0 wasm-path"
echo " wasm-path can be either local file path or https URL"
}

[ $# -ne 1 ] && (usage; exit 1)

function print_divider() {
echo "------------------------------------------------------------"
}

print_divider

# download runtime wasm
echo "Get runtime wasm from $1"
case "$1" in
https*)
wget -q "$1" -O "$output_wasm" ;;
*)
cp -f "$1" "$output_wasm" ;;
esac

echo "Done"

if [ -f "$output_wasm" ]; then
ls -l "$output_wasm"
else
echo "Cannot find $output_wasm, quit"
exit 1
fi

print_divider

# 2. do runtime upgrade and verify
echo "Do runtime upgrade and verify ..."
cd "$ROOTDIR/ts-tests"
echo "NODE_ENV=ci" > .env
yarn && yarn test-runtime-upgrade 2>&1

print_divider

echo "Done"
7 changes: 4 additions & 3 deletions ts-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"register-parachain": "ts-node tests/register-parachain.ts",
"test-filter": "mocha --exit --sort -r ts-node/register 'tests/base-filter.test.ts'",
"test-bridge": "mocha --exit --sort -r ts-node/register 'tests/bridge.test.ts'",
"test-runtime-upgrade": "mocha --exit --sort -r ts-node/register 'tests/runtime-upgrade.test.ts'",
"test-all": "mocha --exit --sort -r ts-node/register 'tests/**/*.test.ts'"
},
"author": "Han Zhao",
Expand All @@ -21,13 +22,13 @@
"gts": "^3.1.0",
"mocha": "^10.0.0",
"mocha-steps": "^1.3.0",
"prettier": "2.7.1",
"prettier": "2.8.1",
"ts-node": "^10.8.1",
"typescript": "^4.7.3"
},
"dependencies": {
"@polkadot/api": "^9.0.1",
"@polkadot/types": "^9.0.1",
"@polkadot/api": "^9.10.1",
"@polkadot/types": "^9.10.1",
"dotenv": "^16.0.1",
"ethers": "^5.6.9",
"web3": "^1.7.3"
Expand Down
4 changes: 2 additions & 2 deletions ts-tests/tests/base-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ describeLitentry('Test Base Filter', ``, (context) => {
// only work for litentry|rococo
const parachain = (await context.api.rpc.system.chain()).toString().toLowerCase();
if (parachain === 'litmus-dev') {
console.log("skip test.")
return
console.log('skip test.');
return;
}
// Get the initial balance of Alice and Bob
const { nonce: aliceInitNonce, data: aliceInitBalance } = await context.api.query.system.account(
Expand Down
8 changes: 4 additions & 4 deletions ts-tests/tests/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describeCrossChainTransfer('Test Cross-chain Transfer', ``, (context) => {
const beforeAccountData = await context.parachainConfig.api.query.system.account(
context.parachainConfig.ferdie.address
);
console.log("before deposit: ", beforeAccountData.toString())
console.log('before deposit: ', beforeAccountData.toString());

// approve
await erc20.approve(context.ethConfig.erc20Handler.address, depositAmount);
Expand All @@ -37,7 +37,7 @@ describeCrossChainTransfer('Test Cross-chain Transfer', ``, (context) => {
const afterAccountData = await context.parachainConfig.api.query.system.account(
context.parachainConfig.ferdie.address
);
console.log("after deposit: ", afterAccountData.toString())
console.log('after deposit: ', afterAccountData.toString());

assert.equal(
bn100e12.add(beforeAccountData.data.free.toBn()).toString(),
Expand Down Expand Up @@ -72,7 +72,7 @@ describeCrossChainTransfer('Test Cross-chain Transfer', ``, (context) => {
handlerBalance
.div(BigNumber.from(1000000))
.add(BigNumber.from(100))
.add(BigNumber.from(fee.toString())),
.add(BigNumber.from(fee.toString())).toString(),
receipt,
0
),
Expand Down Expand Up @@ -107,7 +107,7 @@ describeCrossChainTransfer('Test Cross-chain Transfer', ``, (context) => {
const fee = await context.parachainConfig.api.query.chainBridge.bridgeFee(0);
await signAndSend(
context.parachainConfig.api.tx.bridgeTransfer.transferNative(
handlerBalance.div(BigNumber.from(1000000)).add(BigNumber.from(fee.toString())),
handlerBalance.div(BigNumber.from(1000000)).add(BigNumber.from(fee.toString())).toString(),
receipt,
0
),
Expand Down
71 changes: 71 additions & 0 deletions ts-tests/tests/runtime-upgrade.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { blake2AsHex, cryptoWaitReady } from '@polkadot/util-crypto';
import * as fs from 'fs';
import { Keyring, ApiPromise, WsProvider } from '@polkadot/api';
import { loadConfig, describeLitentry } from './utils';
import '@polkadot/wasm-crypto/initOnlyAsm';
import * as path from 'path';
import { expect } from 'chai';
import { step } from 'mocha-steps';

async function getRuntimeVersion(api: ApiPromise) {
const runtime_version = await api.rpc.state.getRuntimeVersion();
return +runtime_version['specVersion'];
}

async function runtimeUpgrade(api: ApiPromise, wasm: string) {
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
const old_runtime_version = await getRuntimeVersion(api);
let currentBlock = (await api.rpc.chain.getHeader()).number.toNumber();
console.log(`Start doing runtime upgrade, current block = ${currentBlock}`);

// authorize and enact the upgrade
let nonce = (await api.rpc.system.accountNextIndex(alice.address)).toNumber();
await api.tx.sudo
.sudo(api.tx.parachainSystem.authorizeUpgrade(blake2AsHex(wasm)))
.signAndSend(alice, { nonce: -1 });
console.log('Submitted authorizeUpgrade');
await api.tx.parachainSystem.enactAuthorizedUpgrade(wasm).signAndSend(alice, { nonce: -1 });
console.log('Submitted enactAuthorizedUpgrade');

// wait for 10 blocks
console.log('Wait for new runtime ...');
currentBlock = (await api.rpc.chain.getHeader()).number.toNumber();
let timeoutBlock = currentBlock + 10;
let runtimeUpgraded = false;

return new Promise(async (resolve, reject) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Although the whole return value here is a little bit hard to understand, it works fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

const unsub = await api.rpc.chain.subscribeNewHeads(async (header) => {
console.log(`Polling .. block = ${header.number.toNumber()}`);
const runtime_version = await getRuntimeVersion(api);
if (!runtimeUpgraded) {
if (runtime_version > old_runtime_version) {
runtimeUpgraded = true;
console.log(
`Runtime upgrade OK, new runtime version = ${runtime_version}, waiting for 2 more blocks ...`
);
timeoutBlock = header.number.toNumber() + 2;
}
}
if (header.number.toNumber() == timeoutBlock) {
unsub();
if (!runtimeUpgraded) {
reject('Runtime upgrade failed with timeout');
} else {
console.log('All good');
resolve(runtime_version);
}
}
});
});
}

describeLitentry('Runtime upgrade test', ``, (context) => {
step('Running runtime ugprade test', async function () {
const wasmPath = path.resolve('/tmp/runtime.wasm');
const wasm = fs.readFileSync(wasmPath).toString('hex');
const runtimeVersion = await runtimeUpgrade(context.api, `0x${wasm}`);
console.log(`result: ${runtimeVersion}`);
expect(runtimeVersion === (await getRuntimeVersion(context.api)));
});
});
Loading