diff --git a/.github/runtime.json b/.github/runtime.json index 6bc8f44888..e60eb61f49 100644 --- a/.github/runtime.json +++ b/.github/runtime.json @@ -1,14 +1,14 @@ [ { - "name": "rococo", - "package": "rococo-parachain-runtime", - "path": "runtime/rococo", - "uri": "wss://rpc.litentry-parachain.litentry.io:443" + "name": "paseo", + "package": "paseo-parachain-runtime", + "path": "runtime/paseo", + "uri": "wss://rpc.paseo-parachain.litentry.io:443" }, { "name": "litentry", "package": "litentry-parachain-runtime", "path": "runtime/litentry", "uri": "wss://rpc.litentry-parachain.litentry.io:443" - } + } ] \ No newline at end of file diff --git a/.github/workflows/check-runtime-upgrade.yml b/.github/workflows/check-runtime-upgrade.yml index 3e00583aab..7d5c48bc12 100644 --- a/.github/workflows/check-runtime-upgrade.yml +++ b/.github/workflows/check-runtime-upgrade.yml @@ -76,11 +76,6 @@ jobs: - name: Enable corepack and pnpm run: corepack enable && corepack enable pnpm - - name: Fork ${{ matrix.runtime.name }} and launch parachain - timeout-minutes: 20 - run: | - ./scripts/fork-parachain-and-launch.sh ${{ matrix.runtime.name }} - - name: Install subwasm ${{ env.SUBWASM_VERSION }} run: | wget https://github.com/chevdor/subwasm/releases/download/v${{ env.SUBWASM_VERSION }}/subwasm_linux_amd64_v${{ env.SUBWASM_VERSION }}.deb @@ -92,24 +87,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 10 run: | - ./scripts/runtime-upgrade.sh ${{ matrix.runtime.name }}-parachain-runtime.compact.compressed.wasm ${{ env.RELEASE_TAG }} - - - name: Collect docker logs if test fails - continue-on-error: true - uses: jwalton/gh-docker-logs@v2 - if: failure() - with: - tail: all - dest: docker-logs - - - name: Upload docker logs if test fails - uses: actions/upload-artifact@v4 - if: failure() - with: - name: ${{ matrix.runtime.name }}-docker-logs - path: docker-logs - if-no-files-found: ignore - retention-days: 3 + ./parachain/scripts/runtime-upgrade.sh ${{ matrix.runtime.name }} ${{ matrix.runtime.uri }} ${{ env.RELEASE_TAG }} try-runtime: runs-on: ubuntu-22.04 @@ -128,7 +106,7 @@ jobs: fetch-depth: 0 - name: Run ${{ matrix.runtime.name }} try-runtime check - uses: paritytech/try-runtime-gha@v0.2.0 + uses: BillyWooo/try-runtime-gha@v0.3.0 with: runtime-package: ${{ matrix.runtime.package }} node-uri: ${{ matrix.runtime.uri }} diff --git a/parachain/scripts/fork-parachain-and-launch.sh b/parachain/scripts/fork-parachain-and-launch.sh deleted file mode 100755 index 8103ccd165..0000000000 --- a/parachain/scripts/fork-parachain-and-launch.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -# this script: -# - scrapes the state of a given parachain using `fork-off-substrate` -# - save the state snapshot to a chain spec JSON -# - use this chain spec to launch a local parachain network - -ROOTDIR=$(git rev-parse --show-toplevel) - -# setup TMPDIR -export TMPDIR=$(mktemp -d) -cleanup() { - echo "removing $1 ..." - rm -rf "$1" -} -trap 'cleanup $TMPDIR' INT TERM EXIT - -FORK_OFF_SUBSTRATE_REPO="https://github.com/litentry/fork-off-substrate.git" - -function print_divider() { - echo "------------------------------------------------------------" -} - -function usage() { - print_divider - echo "Usage: $0 [chain] [ws-rpc-endpoint] [binary]" - echo - echo "chain: rococo|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 3 ] && (usage; exit 1) - -case "$1" in - help|-h|--help) - usage - exit 1 - ;; - *) - ;; -esac - -ORIG_CHAIN=${1:-rococo} -FORK_CHAIN=${ORIG_CHAIN}-dev - -case "$ORIG_CHAIN" in - rococo) - ENDPOINT="${2:-wss://rpc.litentry-parachain.litentry.io}" - ;; - litentry) - ENDPOINT="${2:-wss://rpc.litentry-parachain.litentry.io}" - ;; - *) - echo "unsupported chain type" - exit 1 ;; -esac - -cd "$TMPDIR" -git clone "$FORK_OFF_SUBSTRATE_REPO" -cd fork-off-substrate -npm i - -mkdir data && cd data - -# copy the binary -if [ -z "$3" ]; then - docker cp "$(docker create --rm litentry/litentry-parachain:latest):/usr/local/bin/litentry-collator" binary -else - cp -f "$3" binary -fi -chmod a+x binary - -# write .env file -cd .. -cat << EOF > .env -WS_RPC_ENDPOINT=$ENDPOINT -ALICE=1 -ORIG_CHAIN=$ORIG_CHAIN -FORK_CHAIN=$FORK_CHAIN -EOF - -npm start - -if [ ! -f data/fork.json ]; then - echo "cannot find data/fork.json, please check it manually" - exit 2 -fi - -cp -f data/fork.json "$ROOTDIR/parachain/docker/" - -cd "$ROOTDIR/parachain" -sed -i.bak "s;$FORK_CHAIN;fork.json;" "docker/$ORIG_CHAIN-parachain-launch-config.yml" - -# start the network -make "launch-docker-$ORIG_CHAIN" \ No newline at end of file diff --git a/parachain/scripts/runtime-upgrade.sh b/parachain/scripts/runtime-upgrade.sh index b2481005fe..1917200512 100755 --- a/parachain/scripts/runtime-upgrade.sh +++ b/parachain/scripts/runtime-upgrade.sh @@ -3,75 +3,103 @@ 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 +new_wasm=/tmp/runtime.wasm function usage() { echo - echo "Usage: $0 wasm-name [release-tag]" + echo "Usage: $0 " + echo "e.g.:" + echo " $0 litentry wss://rpc.litentry-parachain.litentry.io v0.9.21-01" } -[ $# -gt 2 ] && (usage; exit 1) +[ $# -ne 3 ] && (usage; exit 1) function print_divider() { echo "------------------------------------------------------------" } +# Download runtime wasm print_divider +echo "Download $1-parachain-runtime.compact.compressed.wasm from release tag $3 ..." +gh release download "$3" -p "$1-parachain-runtime.compact.compressed.wasm" -O "$new_wasm" || true -# 1. download or copy runtime wasm -if [ -z "$2" ]; then - echo "Copy local wasm $1 ..." - cp -f "$1" "$output_wasm" +if [ -f "$new_wasm" ] && [ -s "$new_wasm" ]; then + ls -l "$new_wasm" else - echo "Download $1 from release tag $2 ..." - gh release download "$2" -p "$1" -O "$output_wasm" || true -fi - -if [ -f "$output_wasm" ] && [ -s "$output_wasm" ]; then - ls -l "$output_wasm" -else - echo "Cannot find $output_wasm or it has 0 bytes, quit" + echo "Cannot find $new_wasm or it has 0 bytes, quit" exit 0 fi +# Install tools print_divider +wget -q https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl -O websocat +chmod +x websocat +echo "Websocat version: $(./websocat --version)" -# 2. check if the released runtime version is greater than the on-chain runtime version, -# which should be now accessible via localhost:9944 -onchain_version=$(curl -s -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getRuntimeVersion", "params": [] }' http://localhost:9944 | jq .result.specVersion) -release_version=$(subwasm --json info "$output_wasm" | jq .core_version.specVersion) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" +echo "nvm version: $(nvm --version)" +# Check if the released runtime version is greater than the on-chain runtime version, +print_divider echo "Check runtime version ..." +release_version=$(subwasm --json info "$new_wasm" | jq .core_version.specVersion) + +PAYLOAD='{"id":1, "jsonrpc":"2.0", "method": "state_getRuntimeVersion", "params": [] }' +RETRY_INTERVAL=5 +MAX_RETRIES=10 +i=0 +while ((i/dev/null) + if [[ -n "$onchain_version" && "$onchain_version" != "null" ]]; then + break + else + echo "Invalid or no response. Retrying in $RETRY_INTERVAL seconds..." + sleep $RETRY_INTERVAL + fi + i=$((i + 1)) +done +if [ "$i" -ge $MAX_RETRIES ]; then + echo "Failed to fetch on-chain version after $MAX_RETRIES attempts." + exit 1 +fi + echo "On-chain: $onchain_version" echo "Release: $release_version" if [ -n "$release_version" ] && \ [ -n "$onchain_version" ] && \ [ "$onchain_version" -ge "$release_version" ]; then - echo "Runtime version not increased, quit" - exit 0 + echo "Current On-chain runtime is up to date, quit" + exit 1 fi +# 4. do runtime upgrade and verify print_divider - -# 3. do runtime upgrade and verify echo "Do runtime upgrade and verify ..." -cd "$ROOTDIR/parachain/ts-tests" -echo "NODE_ENV=ci" > .env -pnpm install && pnpm run test-runtime-upgrade 2>&1 + +nvm install 20 +echo "start chopsticks: $1" +npx @acala-network/chopsticks@1.0.1 --endpoint=$2 --port=9944 --mock-signature-host=true --db=./new-db.sqlite --runtime-log-level=5 --allow-unresolved-imports=true --wasm-override $new_wasm & +PID=$! +echo "Chopsticks fork parachain PID: $PID" +sleep 30 + +echo "after chopsticks: $1" +new_onchain_version=$(curl -s -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getRuntimeVersion", "params": [] }' http://localhost:9944 | jq .result.specVersion) +if [ -n "$new_onchain_version" ] && \ + [ "$new_onchain_version" -ne "$release_version" ]; then + echo "On-chain new: $new_onchain_version" + echo "Runtime version NOT increased successfully, quit" + exit 1 +fi + +echo "Runtime upgrade succeed: $new_onchain_version" print_divider +echo "Done" -echo "Done" \ No newline at end of file diff --git a/parachain/ts-tests/integration-tests/runtime-upgrade.test.ts b/parachain/ts-tests/integration-tests/runtime-upgrade.test.ts deleted file mode 100644 index 9038fdb60a..0000000000 --- a/parachain/ts-tests/integration-tests/runtime-upgrade.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { blake2AsHex } from '@polkadot/util-crypto'; -import * as fs from 'fs'; -import { Keyring, ApiPromise } from '@polkadot/api'; -import { describeLitentry } from '../common/utils/integration-setup'; -import '@polkadot/wasm-crypto/initOnlyAsm'; -import * as path from 'path'; -import { expect } from 'chai'; -import { step } from 'mocha-steps'; -import { signAndSend, subscribeToEvents, observeEvent } from '../common/utils'; -import { KeyringPair } from '@polkadot/keyring/types'; -import { Event } from '@polkadot/types/interfaces/system'; -import { ApiTypes, SubmittableExtrinsic } from '@polkadot/api/types'; -async function getRuntimeVersion(api: ApiPromise) { - const runtime_version = await api.rpc.state.getRuntimeVersion(); - return +runtime_version['specVersion']; -} - -async function waitForRuntimeUpgrade(api: ApiPromise, oldRuntimeVersion: number): Promise { - return new Promise(async (resolve, reject) => { - let runtimeUpgraded = false; - let timeoutBlock = (await api.rpc.chain.getHeader()).number.toNumber() + 10; - - const unsub = await api.rpc.chain.subscribeNewHeads(async (header) => { - console.log(`Polling .. block = ${header.number.toNumber()}`); - const runtimeVersion = await getRuntimeVersion(api); - if (!runtimeUpgraded) { - if (runtimeVersion > oldRuntimeVersion) { - runtimeUpgraded = true; - console.log( - `Runtime upgrade OK, new runtime version = ${runtimeVersion}, 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(runtimeVersion); - } - } - }); - }); -} - -async function excuteNotePreimage(api: ApiPromise, signer: KeyringPair, encoded: string) { - const notePreimageTx = api.tx.preimage.notePreimage(encoded); - const eventsPromise = subscribeToEvents('preimage', 'Noted', api); - await signAndSend(notePreimageTx, signer); - const notePreimageEvent = (await eventsPromise).map(({ event }) => event); - expect(notePreimageEvent.length === 1, 'Note preimage failed'); - console.log('Preimage noted ✅'); -} - -async function excuteCouncilProposal( - api: ApiPromise, - signer: KeyringPair, - proposal: SubmittableExtrinsic -): Promise { - return new Promise(async (resolve) => { - const proposalTx = api.tx.council.propose(2, proposal, proposal.encodedLength); - const eventsPromise = subscribeToEvents('council', 'Proposed', api); - await signAndSend(proposalTx, signer); - const proposalTxEvent = (await eventsPromise).map(({ event }) => event); - expect(proposalTxEvent.length === 1, 'Council proposal failed'); - console.log('Council Proposed ✅'); - resolve(proposalTxEvent); - }); -} - -async function excuteTechnicalCommitteeProposal( - api: ApiPromise, - signer: KeyringPair, - encodedHash: string -): Promise { - const proposal = api.tx.democracy.fastTrack(encodedHash, 10, 1); - const eventsPromise = subscribeToEvents('technicalCommittee', 'Executed', api); - const techCommitteeProposalTx = api.tx.technicalCommittee.propose(1, proposal, proposal.encodedLength); - await signAndSend(techCommitteeProposalTx, signer); - const democracyStartedEvent = (await eventsPromise).map(({ event }) => event); - expect(democracyStartedEvent.length === 1); - console.log('Tech committee proposal executed ✅'); -} - -/// Pushes a polkadot runtime update via governance. -/// preimage => council proposal => vote => democracy pass => fast track => democracy proposal => democracy vote => enactAuthorizedUpgrade. -async function runtimeupgradeViaGovernance(api: ApiPromise, wasm: string) { - const keyring = new Keyring({ type: 'sr25519' }); - const alice = keyring.addFromUri('//Alice'); - const bob = keyring.addFromUri('//Bob'); - - const old_runtime_version = await getRuntimeVersion(api); - console.log(`Old runtime version = ${old_runtime_version}`); - - const encoded = api.tx.parachainSystem.authorizeUpgrade(blake2AsHex(wasm), false).method.toHex(); - const encodedHash = blake2AsHex(encoded); - console.log(`Preimage hash: ${encodedHash}`); - - // Submit the preimage (if it doesn't already exist) - let preimageStatus = (await api.query.preimage.statusFor(encodedHash)).toHuman(); - if (!preimageStatus) { - await excuteNotePreimage(api, alice, encoded); - } - const externalMotion = api.tx.democracy.externalProposeMajority({ Legacy: encodedHash }); - - // propose the council proposal - const proposedEvent = await excuteCouncilProposal(api, alice, externalMotion); - const proposalHash = proposedEvent[0].data[2].toString(); - const proposalIndex = Number(proposedEvent[0].data[1].toHuman()); - - // vote on the council proposal - const voteTx = api.tx.council.vote(proposalHash, proposalIndex, true); - const voteEventsPromise = subscribeToEvents('council', 'Voted', api); - - await Promise.all([await signAndSend(voteTx, alice), await signAndSend(voteTx, bob)]); - const voteTxEvent = (await voteEventsPromise).map(({ event }) => event); - expect(voteTxEvent.length === 2); - console.log('Alice Bob council Voted ✅'); - - // close the council proposal - const councilCloseTx = api.tx.council.close( - proposalHash, - proposalIndex, - { - refTime: 1_000_000_000, - proofSize: 1_000_000, - }, - externalMotion.encodedLength - ); - const closeEventsPromise = subscribeToEvents('council', 'Closed', api); - await signAndSend(councilCloseTx, alice); - const councilCloseEvent = (await closeEventsPromise).map(({ event }) => event); - expect(councilCloseEvent.length === 1); - console.log('Council Closed ✅'); - - // fast track the democracy proposal - await excuteTechnicalCommitteeProposal(api, alice, encodedHash); - - // vote on the democracy proposal - const democracyVoteEventsPromise = subscribeToEvents('democracy', 'Voted', api); - const referendumCount = (await api.query.democracy.referendumCount()).toNumber(); - const democracyVoteTx = api.tx.democracy.vote(referendumCount - 1, { - Standard: { vote: true, balance: 1_00_000_000_000_000 }, - }); - await Promise.all([await signAndSend(democracyVoteTx, alice), await signAndSend(democracyVoteTx, bob)]); - const democracyVoteEvent = (await democracyVoteEventsPromise).map(({ event }) => event); - expect(democracyVoteEvent.length === 2); - console.log('Alice Bob democracy Voted ✅'); - - console.log('Waiting for democracy to pass...'); - await observeEvent('democracy', 'Passed', api); - console.log('Democracy passed ✅'); - - console.log('Waiting for parachainSystem upgrade authorize...'); - await observeEvent('parachainSystem', 'UpgradeAuthorized', api); - console.log('parachainSystem upgrade authorized ✅'); - - // enact the upgrade - const parachainSystemScheduleUpgradeTx = api.tx.parachainSystem.enactAuthorizedUpgrade(wasm); - await signAndSend(parachainSystemScheduleUpgradeTx, alice); - - console.log('Waiting for runtime upgrade to be applied...'); - await observeEvent('parachainSystem', 'ValidationFunctionApplied', api); - console.log('Runtime upgrade applied ✅'); - - const newRuntimeVersion = await waitForRuntimeUpgrade(api, old_runtime_version); - console.log(`New runtime version = ${newRuntimeVersion}`); - return newRuntimeVersion; -} -describeLitentry('Runtime upgrade test', ``, (context) => { - step('Running runtime ugprade test', async function () { - let runtimeVersion: number; - const wasmPath = path.resolve('/tmp/runtime.wasm'); - const wasm = fs.readFileSync(wasmPath).toString('hex'); - runtimeVersion = await runtimeupgradeViaGovernance(context.api, `0x${wasm}`); - - expect(runtimeVersion === (await getRuntimeVersion(context.api))); - - console.log('Runtime upgraded ✅'); - }); -}); diff --git a/parachain/ts-tests/package.json b/parachain/ts-tests/package.json index 236801cee5..79596fb86d 100644 --- a/parachain/ts-tests/package.json +++ b/parachain/ts-tests/package.json @@ -15,7 +15,6 @@ "test-filter": "pnpm exec mocha --exit --sort -r ts-node/register 'integration-tests/base-filter.test.ts'", "test-bridge": "pnpm exec mocha --exit --sort -r ts-node/register 'integration-tests/bridge.test.ts'", "test-evm-contract": "pnpm exec mocha --exit --sort -r ts-node/register 'integration-tests/evm-contract.test.ts'", - "test-runtime-upgrade": "pnpm exec mocha --exit --sort -r ts-node/register 'integration-tests/runtime-upgrade.test.ts'", "test-all": "pnpm exec mocha --exit --sort -r ts-node/register 'integration-tests/**/*.test.ts'", "test-precompile-contract": "pnpm exec mocha --exit --sort -r ts-node/register 'integration-tests/precompile-contract.test.ts'", "format": "pnpm exec prettier --write '**.ts'"