@moonsong-labs/moonwall-cli
Version:
Testing framework for the Moon family of projects
157 lines (154 loc) • 6.39 kB
JavaScript
import {
blake2AsHex,
cancelReferendaWithCouncil,
executeProposalWithCouncil
} from "./chunk-DTYVZ3AY.js";
import {
getRuntimeWasm
} from "./chunk-BWH5WBXI.js";
// src/lib/upgrade.ts
import "@moonbeam-network/api-augment";
import fs, { readFileSync } from "fs";
import chalk from "chalk";
import { sha256 } from "ethers";
import { alith } from "@moonsong-labs/moonwall-util";
async function upgradeRuntimeChopsticks(context, path) {
const rtWasm = readFileSync(path);
const rtHex = `0x${rtWasm.toString("hex")}`;
const rtHash = blake2AsHex(rtHex);
await context.setStorage({
module: "parachainSystem",
method: "authorizedUpgrade",
methodParams: rtHash
});
await context.createBlock();
const api = context.getSubstrateApi();
await api.tx.parachainSystem.enactAuthorizedUpgrade(rtHex).signAndSend(alith);
await context.createBlock({ count: 3 });
}
async function upgradeRuntime(api, preferences) {
const options = {
from: alith,
waitMigration: true,
useGovernance: false,
...preferences
};
return new Promise(async (resolve, reject) => {
try {
const code = fs.readFileSync(
await getRuntimeWasm(options.runtimeName, options.runtimeTag, options.localPath)
).toString();
const existingCode = await api.rpc.state.getStorage(":code");
if (existingCode.toString() == code) {
reject(
`Runtime upgrade with same code: ${existingCode.toString().slice(0, 20)} vs ${code.toString().slice(0, 20)}`
);
}
let nonce = (await api.rpc.system.accountNextIndex(options.from.address)).toNumber();
if (options.useGovernance) {
let proposal = api.tx.parachainSystem.authorizeUpgrade(blake2AsHex(code));
let encodedProposal = proposal.method.toHex();
let encodedHash = blake2AsHex(encodedProposal);
const preImageExists = api.query.preimage && await api.query.preimage.statusFor(encodedHash);
const democracyPreImageExists = !api.query.preimage && await api.query.democracy.preimages(encodedHash);
if (api.query.preimage && preImageExists.isSome && preImageExists.unwrap().isRequested) {
process.stdout.write(`Preimage ${encodedHash} already exists !
`);
} else if (
// TODO: remove support for democracy preimage support after 2000
!api.query.preimage && democracyPreImageExists.isSome && democracyPreImageExists.unwrap().isAvailable
) {
process.stdout.write(`Preimage ${encodedHash} already exists !
`);
} else {
process.stdout.write(
`Registering preimage (${sha256(Buffer.from(code))} [~${Math.floor(
code.length / 1024
)} kb])...`
);
if (api.query.preimage) {
await api.tx.preimage.notePreimage(encodedProposal).signAndSend(options.from, { nonce: nonce++ });
} else {
await api.tx.democracy.notePreimage(encodedProposal).signAndSend(options.from, { nonce: nonce++ });
}
process.stdout.write(`\u2705
`);
}
const referendum = await api.query.democracy.referendumInfoOf.entries();
const referendaIndex = api.query.preimage ? referendum.filter(
(ref) => ref[1].unwrap().isOngoing && ref[1].unwrap().asOngoing.proposal.isLookup && ref[1].unwrap().asOngoing.proposal.asLookup.hash.toHex() == encodedHash
).map(
(ref) => api.registry.createType("u32", ref[0].toU8a().slice(-4)).toNumber()
)?.[0] : referendum.filter(
(ref) => ref[1].unwrap().isOngoing && ref[1].unwrap().asOngoing.proposalHash.toHex() == encodedHash
).map(
(ref) => api.registry.createType("u32", ref[0].toU8a().slice(-4)).toNumber()
)?.[0];
if (referendaIndex !== null && referendaIndex !== void 0) {
process.stdout.write(`Vote for upgrade already in referendum, cancelling it.
`);
await cancelReferendaWithCouncil(api, referendaIndex);
}
await executeProposalWithCouncil(api, encodedHash);
nonce = (await api.rpc.system.accountNextIndex(options.from.address)).toNumber();
process.stdout.write(`Enacting authorized upgrade...`);
await api.tx.parachainSystem.enactAuthorizedUpgrade(code).signAndSend(options.from, { nonce: nonce++ });
process.stdout.write(`\u2705
`);
} else {
process.stdout.write(
`Sending sudo.setCode (${sha256(Buffer.from(code))} [~${Math.floor(
code.length / 1024
)} kb])...`
);
const isWeightV1 = !api.registry.createType("Weight").proofSize;
await api.tx.sudo.sudoUncheckedWeight(
await api.tx.system.setCodeWithoutChecks(code),
isWeightV1 ? "1" : {
proofSize: 1,
refTime: 1
}
).signAndSend(options.from, { nonce: nonce++ });
process.stdout.write(`\u2705
`);
}
process.stdout.write(`Waiting to apply new runtime (${chalk.red(`~4min`)})...`);
let isInitialVersion = true;
const unsub = await api.rpc.state.subscribeRuntimeVersion(async (version) => {
if (!isInitialVersion) {
const blockNumber = (await api.rpc.chain.getHeader()).number.toNumber();
console.log(
`\u2705 [${version.implName}-${version.specVersion} ${existingCode.toString().slice(0, 6)}...] [#${blockNumber}]`
);
unsub();
const newCode = await api.rpc.state.getStorage(":code");
if (newCode.toString() != code) {
reject(
`Unexpected new code: ${newCode.toString().slice(0, 20)} vs ${code.toString().slice(0, 20)}`
);
}
if (options.waitMigration) {
const blockToWait = (await api.rpc.chain.getHeader()).number.toNumber() + 1;
await new Promise(async (resolve2) => {
const subBlocks = await api.rpc.chain.subscribeNewHeads(async (header) => {
if (header.number.toNumber() == blockToWait) {
subBlocks();
resolve2(blockToWait);
}
});
});
}
resolve(blockNumber);
}
isInitialVersion = false;
});
} catch (e) {
console.error(`Failed to setCode`);
reject(e);
}
});
}
export {
upgradeRuntimeChopsticks,
upgradeRuntime
};