UNPKG

@zombienet/orchestrator

Version:

ZombieNet aim to be a testing framework for substrate based blockchains, providing a simple cli tool that allow users to spawn and test ephemeral Substrate based networks

147 lines (146 loc) 7.29 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.chainUpgradeFromUrl = chainUpgradeFromUrl; exports.chainUpgradeFromLocalFile = chainUpgradeFromLocalFile; exports.chainCustomSectionUpgrade = chainCustomSectionUpgrade; exports.validateRuntimeCode = validateRuntimeCode; const api_1 = require("@polkadot/api"); const util_crypto_1 = require("@polkadot/util-crypto"); const fs_1 = require("fs"); const napi_maybe_compressed_blob_1 = require("napi-maybe-compressed-blob"); const constants_1 = require("../constants"); const debug = require("debug")("zombie::js-helpers::chain-upgrade"); function chainUpgradeFromUrl(api, wasmFileUrl) { return __awaiter(this, void 0, void 0, function* () { // 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 from url: ${wasmFileUrl}`); const fetchResponse = yield fetch(wasmFileUrl); const file = yield fetchResponse.arrayBuffer(); const buff = Buffer.from(file); const hash = (0, util_crypto_1.blake2AsHex)(buff); yield performChainUpgrade(api, buff.toString("hex")); return hash; }); } function chainUpgradeFromLocalFile(api, filePath) { return __awaiter(this, void 0, void 0, function* () { // 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 from path: ${filePath}`); const data = yield fs_1.promises.readFile(filePath); const buff = Buffer.from(data); const hash = (0, util_crypto_1.blake2AsHex)(buff); yield performChainUpgrade(api, buff.toString("hex")); return hash; }); } // Add a custom section to the end, re-compress and perform the upgrade of the runtime. // It's required by the standard that custom sections cannot have any semantic differences // and can be ignored in the general case. // The wasm format consists of bunch of sections. Here we just slap a custom section to the end. function chainCustomSectionUpgrade(api) { return __awaiter(this, void 0, void 0, function* () { const code = yield api.rpc.state.getStorage(":code"); const codeHex = code.toString().slice(2); const codeBuf = Buffer.from(hexToBytes(codeHex)); const decompressed = (0, napi_maybe_compressed_blob_1.decompress)(codeBuf); // add a custom section // Same as echo -n -e "\x00\x07\x05\x64\x75\x6D\x6D\x79\x0A" >> file.wasm const customSection = [0x00, 0x07, 0x05, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x0a]; const withCustomSectionCode = Buffer.concat([ decompressed, Buffer.from(customSection), ]); // compress again const compressed = (0, napi_maybe_compressed_blob_1.compress)(withCustomSectionCode); const hash = (0, util_crypto_1.blake2AsHex)(compressed); debug(`New compressed hash : ${hash}`); yield performChainUpgrade(api, compressed.toString("hex")); return hash; }); } function validateRuntimeCode(api_2, paraId_1, hash_1) { return __awaiter(this, arguments, void 0, function* (api, paraId, hash, timeout = constants_1.DEFAULT_INDIVIDUAL_TEST_TIMEOUT) { const validate = (hash) => __awaiter(this, void 0, void 0, function* () { let done; while (!done) { const currentHash = yield api.query.paras.currentCodeHash(paraId); console.log(`parachain ${paraId} current code hash : ${currentHash}`); if (hash === currentHash.toString()) break; // wait 2 secs between checks yield new Promise((resolve) => setTimeout(resolve, 2000)); } return true; }); const resp = yield Promise.race([ validate(hash), new Promise((resolve) => setTimeout(() => { const err = new Error(`Timeout(${timeout}), "validating the hash of the runtime upgrade`); return resolve(err); }, timeout * 1000)), ]); if (resp instanceof Error) throw resp; return resp; }); } function performChainUpgrade(api, code) { return __awaiter(this, void 0, void 0, function* () { yield (0, util_crypto_1.cryptoWaitReady)(); const keyring = new api_1.Keyring({ type: "sr25519" }); const alice = keyring.addFromUri("//Alice"); yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const unsub = yield api.tx.sudo .sudoUncheckedWeight(api.tx.system.setCodeWithoutChecks(`0x${code}`), { refTime: 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) { unsub(); if (result.status.isInvalid) { // Allow `invalid` tx, since we will validate the hash of the `code` later // The problem being that there are forks // Block X is build with the tx included // X gets retracted by Y that doesn't has the tx included // And thus the tx lands in the tx pool again // Then another block on top of x is build that tries to include the tx again and boom // It reports it as invalid, because it was already included console.log(`Transaction invalid:`, JSON.stringify(result)); return resolve(); } else { console.log(`Transaction Error:`, JSON.stringify(result)); return reject(); } } }); })); }); } /// Internal function hexToBytes(hex) { const bytes = []; for (let c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16)); return bytes; }