-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Script to upgrade a Centrifuge parachain live (#1884)
* Add script to upgrade a live parachain (testnet) with sudo * Separate council upgrades and rename folder * use development wasm for demo * use development wasm for demo * remove council proposal and adapt to producion live parachains * fix: upgrade script * fix: bash script * refactor: remove unused config * refactor: replace npm lock with yarn lock * refactor: remove unused noise * feat: improve error catching --------- Co-authored-by: William Freudenberger <[email protected]>
- Loading branch information
Showing
10 changed files
with
782 additions
and
0 deletions.
There are no files selected for viewing
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"endpoint": "wss://fullnode.demo.k-f.dev", | ||
"wasmFile": "./development.wasm", | ||
"privateKey": "", | ||
"sudo": true, | ||
"councilMembers": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"endpoint": "wss://fullnode.development.cntrfg.com", | ||
"wasmFile": "./development.wasm", | ||
"privateKey": "//Alice", | ||
"sudo": true, | ||
"councilMembers": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api'); | ||
const { u8aToHex } = require('@polkadot/util'); | ||
const { blake2AsHex, blake2AsU8a } = require('@polkadot/util-crypto'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
// Load configuration | ||
const configPath = path.resolve(__dirname, 'config.json'); | ||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); | ||
|
||
const run = async () => { | ||
let exitCode = 0; | ||
try { | ||
// Validate configuration | ||
if (!config.endpoint || !config.wasmFile || !config.privateKey) { | ||
console.error("Missing configuration parameters. Please ensure 'endpoint', 'wasmFile', and 'privateKey' are specified in the corresponding configs/*.json."); | ||
process.exit(1); | ||
} | ||
|
||
console.log("Configuration loaded:", config); | ||
|
||
const wsProvider = new WsProvider(config.endpoint); | ||
const api = await ApiPromise.create({ provider: wsProvider }); | ||
|
||
console.log("Connected to the parachain at:", config.endpoint); | ||
|
||
const keyring = new Keyring({ type: "sr25519" }); | ||
let user; | ||
if (config.privateKey.startsWith('//')) { | ||
user = keyring.addFromUri(config.privateKey); | ||
} else { | ||
user = keyring.addFromSeed(config.privateKey); | ||
} | ||
|
||
console.log(`Using account: ${user.address}`); | ||
|
||
const wasm = fs.readFileSync(config.wasmFile); | ||
const wasmHash = blake2AsHex(wasm); | ||
const wasmBytes = u8aToHex(wasm); | ||
|
||
console.log("WASM file loaded and ready for deployment"); | ||
|
||
if (config.sudo) { | ||
console.log("Using sudo to perform the runtime upgrade"); | ||
await sudoAuthorize(api, user, wasmHash); | ||
await enactUpgrade(api, user, wasmBytes); | ||
} else { | ||
console.error("Unsupported"); | ||
} | ||
// Check for specific events or transaction success as needed | ||
} catch (error) { | ||
console.error('Error:', error); | ||
exitCode = 1; | ||
} finally { | ||
process.exit(exitCode); | ||
} | ||
}; | ||
|
||
async function sudoAuthorize(api, sudoAccount, wasmHex) { | ||
const nonce = await api.rpc.system.accountNextIndex(sudoAccount.address) | ||
|
||
return new Promise(async (resolve, reject) => { | ||
try { | ||
// Authorize the upgrade | ||
const authorizeTx = api.tx.sudo.sudo( | ||
api.tx.parachainSystem.authorizeUpgrade(wasmHex, true) | ||
); | ||
|
||
const unsub = await authorizeTx.signAndSend(sudoAccount, { nonce }, ({ status, dispatchError, events }) => { | ||
console.log(`Authorizing upgrade with status ${status}`); | ||
if (status.isInBlock) { | ||
console.log(`Authorization included in block ${status.asInBlock}`); | ||
resolve(); | ||
unsub(); | ||
} | ||
checkError(api, reject, dispatchError, events) | ||
}); | ||
} | ||
catch (error) { | ||
reject(error) | ||
} | ||
}); | ||
} | ||
|
||
async function enactUpgrade(api, sudoAccount, wasmFile) { | ||
const nonce = await api.rpc.system.accountNextIndex(sudoAccount.address) | ||
|
||
return new Promise(async (resolve, reject) => { | ||
try { | ||
// Enact the authorized upgrade | ||
const enactTx = api.tx.parachainSystem.enactAuthorizedUpgrade(wasmFile); | ||
|
||
const unsub = await enactTx.signAndSend(sudoAccount, { nonce }, ({ status, dispatchError, events }) => { | ||
console.log(`Enacting upgrade with status ${status}`); | ||
if (status.isInBlock) { | ||
console.log(`Enactment included in block ${status}`); | ||
resolve(); | ||
unsub(); | ||
} | ||
checkError(api, reject, dispatchError, events) | ||
}); | ||
} | ||
catch (error) { | ||
reject(error) | ||
} | ||
}); | ||
} | ||
|
||
function checkError(api, reject, dispatchError, events) { | ||
if (dispatchError) { | ||
if (dispatchError.isModule) { | ||
// for module errors, we have the section indexed, lookup | ||
const decoded = api.registry.findMetaError(dispatchError.asModule); | ||
const { docs, name, section } = decoded; | ||
|
||
console.error(`${section}.${name}: ${docs.join(' ')}`); | ||
} else { | ||
// Other, CannotLookup, BadOrigin, no extra info | ||
console.error(dispatchError.toString()); | ||
} | ||
reject(dispatchError) | ||
} else if (events) { | ||
events | ||
// find/filter for failed events | ||
.filter(({ event }) => | ||
api.events.system.ExtrinsicFailed.is(event) | ||
) | ||
// we know that data for system.ExtrinsicFailed is | ||
// (DispatchError, DispatchInfo) | ||
.forEach(({ event: { data: [error, info] } }) => { | ||
if (error.isModule) { | ||
// for module errors, we have the section indexed, lookup | ||
const decoded = api.registry.findMetaError(error.asModule); | ||
const { docs, method, section } = decoded; | ||
const error = `${section}.${method}: ${docs.join(' ')}` | ||
|
||
console.error(error); | ||
reject(error) | ||
} else { | ||
// Other, CannotLookup, BadOrigin, no extra info | ||
console.error(error.toString()); | ||
reject(error.toString()) | ||
} | ||
}); | ||
} | ||
} | ||
|
||
run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "parachain-upgrade", | ||
"version": "1.0.0", | ||
"description": "Script to handle parachain upgrades", | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "node index.js" | ||
}, | ||
"dependencies": { | ||
"@polkadot/api": "^11.3.1", | ||
"@polkadot/util": "^12.5.1", | ||
"@polkadot/util-crypto": "^12.5.1" | ||
}, | ||
"author": "", | ||
"license": "ISC" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#!/bin/bash | ||
echo "Please select the environment (development, demo):" | ||
read -r ENVIRONMENT | ||
|
||
# Check if the privateKey is empty for demo environment | ||
if [ "$ENVIRONMENT" == "demo" ]; then | ||
PRIVATE_KEY=$(jq -r '.privateKey' ./configs/demo.json) | ||
if [ -z "$PRIVATE_KEY" ]; then | ||
echo "Error: privateKey is empty in ./configs/demo.json. Please retrieve it from 1Password." | ||
exit 1 | ||
fi | ||
fi | ||
|
||
# # Install NVM and node if not present in your mac: | ||
# brew install nvm && echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc && echo '[ -s "$NVM_DIR/nvm.sh" ] \ | ||
# && \. "$NVM_DIR/nvm.sh"' >> ~/.zshrc && source ~/.zshrc && nvm install node | ||
|
||
# Define the tag and calculate the short git hash | ||
TAG="v0.11.1-rc1" | ||
GIT_HASH=$(git rev-parse --short=7 $TAG) | ||
|
||
# Download the WASM file from Google Cloud Storage | ||
echo "Downloading WASM file..." | ||
if [ "$ENVIRONMENT" == "demo" ]; then | ||
gsutil cp gs://centrifuge-wasm-repo/development/development-"$GIT_HASH".wasm ./"${ENVIRONMENT}".wasm | ||
else | ||
gsutil cp gs://centrifuge-wasm-repo/"${ENVIRONMENT}"/"${ENVIRONMENT}"-"$GIT_HASH".wasm ./"${ENVIRONMENT}".wasm | ||
fi | ||
|
||
# Copy the corresponding configuration file | ||
echo "Copying configuration file..." | ||
cp ./configs/"${ENVIRONMENT}".json ./config.json | ||
|
||
# Run the node script | ||
echo "Running node index.js..." | ||
node index.js | ||
echo "Cleaning up..." | ||
rm ./config.json | ||
rm ./"${ENVIRONMENT}".wasm | ||
|
Oops, something went wrong.