Skip to content

Commit

Permalink
feat: automate compatible CFE upgrades (#4149)
Browse files Browse the repository at this point in the history
* feat: bouncer bump release version

* chore: compare SemVer versions function

* feat: (bouncer) upgrade network command

* refactor: pull spec version check out of compileBinaries

* chore: move compileBinaries to its own file

* chore: lint

* chore: remove unused promptUser

* prettier

* chore: remove stale comment

* fix: use && between commands
  • Loading branch information
kylezs authored Oct 26, 2023
1 parent 9ef3413 commit 20e67c3
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 34 deletions.
3 changes: 2 additions & 1 deletion bouncer/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
pnpm-lock.yaml
.idea
.idea
tmp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@
//
// Optional args:
// -test: Run the swap tests after the upgrade.
//
// For example ./commands/noop_runtime_upgrade.ts
// NB: It *must* be run from the bouncer directory.

import { noopRuntimeUpgrade } from '../shared/noop_runtime_upgrade';
import path from 'path';
import { simpleRuntimeUpgrade } from '../shared/simple_runtime_upgrade';
import { testAllSwaps } from '../shared/swapping';
import { runWithTimeout } from '../shared/utils';

async function main(): Promise<void> {
await noopRuntimeUpgrade();
await simpleRuntimeUpgrade(path.dirname(process.cwd()));

if (process.argv[2] === '-test') {
await testAllSwaps();
Expand Down
36 changes: 36 additions & 0 deletions bouncer/commands/upgrade_network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env -S pnpm tsx
// INSTRUCTIONS
// Upgrades a localnet network to a new version.
// Start a network with the version you want to upgrade from. Then run this command, providing the git reference (commit, branch, tag) you wish to upgrade to.
//
// Optional args:
// patch/minor/major: If the version of the commit we're upgrading to is the same as the version of the commit we're upgrading from, we bump the version by the specified level.
//
// For example: ./commands/upgrade_network.ts v0.10.1
// or: ./commands/upgrade_network.ts v0.10.1 patch

import { upgradeNetwork } from '../shared/upgrade_network';
import { runWithTimeout } from '../shared/utils';

async function main(): Promise<void> {
const upgradeTo = process.argv[2]?.trim();

if (!upgradeTo) {
console.error('Please provide a git reference to upgrade to.');
process.exit(-1);
}

const optBumptTo: string = process.argv[3]?.trim().toLowerCase();
if (optBumptTo === 'patch' || optBumptTo === 'minor' || optBumptTo === 'major') {
await upgradeNetwork(upgradeTo, optBumptTo);
} else {
await upgradeNetwork(upgradeTo);
}

process.exit(0);
}

runWithTimeout(main(), 15 * 60 * 1000).catch((error) => {
console.error(error);
process.exit(-1);
});
1 change: 1 addition & 0 deletions bouncer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"md5": "^2.3.0",
"minimist": "^1.2.8",
"tiny-secp256k1": "^2.2.1",
"toml": "^3.0.0",
"web3": "^1.9.0"
},
"devDependencies": {
Expand Down
7 changes: 7 additions & 0 deletions bouncer/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions bouncer/shared/bump_release_version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { execSync } from 'child_process';

export type SemVerLevel = 'major' | 'minor' | 'patch';

// Bumps the version of all the packages in the workspace by the specified level.
export async function bumpReleaseVersion(level: SemVerLevel, projectRoot: string) {
console.log(`Bumping the version of all packages in the workspace by ${level}...`);
try {
execSync(`cd ${projectRoot} && cargo ws version ${level} --no-git-commit -y`);
} catch (error) {
console.log(error);
console.log('Ensure you have cargo workspaces installed: `cargo install cargo-workspaces`');
}
}
17 changes: 0 additions & 17 deletions bouncer/shared/prompt_user.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
// Do a runtime upgrade that does nothing - the runtime should be identical except for the `spec_version` field.
// Needs to be run from the bouncer directory.
import { execSync } from 'node:child_process';

import { submitRuntimeUpgrade } from './submit_runtime_upgrade';
import { jsonRpc } from './json_rpc';
import { getChainflipApi, observeEvent } from './utils';
import { bumpSpecVersion } from './utils/bump_spec_version';
import { compileBinaries } from './utils/compile_binaries';

async function getCurrentSpecVersion(): Promise<number> {
return Number((await jsonRpc('state_getRuntimeVersion', [], 9944)).specVersion);
}

export async function noopRuntimeUpgrade(): Promise<void> {
// Do a runtime upgrade using the code in the projectRoot directory.
export async function simpleRuntimeUpgrade(projectRoot: string): Promise<void> {
const chainflip = await getChainflipApi();

const currentSpecVersion = await getCurrentSpecVersion();

console.log('Current spec_version: ' + currentSpecVersion);

const nextSpecVersion = currentSpecVersion + 1;
bumpSpecVersion(`${projectRoot}/state-chain/runtime/src/lib.rs`, nextSpecVersion);

bumpSpecVersion('../state-chain/runtime/src/lib.rs', nextSpecVersion);

console.log('Building the new runtime');
execSync('cd ../state-chain/runtime && cargo build --release');
await compileBinaries('runtime', projectRoot);

console.log('Built the new runtime. Applying runtime upgrade.');
console.log('Applying runtime upgrade.');
await submitRuntimeUpgrade(
'../target/release/wbuild/state-chain-runtime/state_chain_runtime.compact.compressed.wasm',
`${projectRoot}/target/release/wbuild/state-chain-runtime/state_chain_runtime.compact.compressed.wasm`,
);

await observeEvent('system:CodeUpdated', chainflip);
Expand Down
85 changes: 85 additions & 0 deletions bouncer/shared/upgrade_network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { execSync } from 'child_process';
import fs from 'fs/promises';
import * as toml from 'toml';
import path from 'path';
import { SemVerLevel, bumpReleaseVersion } from './bump_release_version';
import { simpleRuntimeUpgrade } from './simple_runtime_upgrade';
import { compareSemVer } from './utils';

async function readPackageTomlVersion(projectRoot: string): Promise<string> {
const data = await fs.readFile(path.join(projectRoot, '/state-chain/runtime/Cargo.toml'), 'utf8');
const parsedData = toml.parse(data);
const version = parsedData.package.version;
return version;
}

// The javascript version of state-chain/primitives/src/lib.rs - SemVer::is_compatible_with()
function isCompatibleWith(semVer1: string, semVer2: string) {
const [major1, minor1] = semVer1.split('.').map(Number);
const [major2, minor2] = semVer2.split('.').map(Number);

return major1 === major2 && minor1 === minor2;
}

// Create a git workspace in the tmp/ directory and check out the specified commit.
// Remember to delete it when you're done!
function createGitWorkspaceAt(absoluteWorkspacePath: string, toGitRef: string) {
try {
// Create a directory for the new workspace
execSync(`mkdir -p ${absoluteWorkspacePath}`);

// Create a new workspace using git worktree.
execSync(`git worktree add ${absoluteWorkspacePath}`);

// Navigate to the new workspace and checkout the specific commit
execSync(`cd ${absoluteWorkspacePath} && git checkout ${toGitRef}`);

console.log('Commit checked out successfully in new workspace.');
} catch (error) {
console.error(`Error: ${error}`);
}
}

// Upgrades a bouncer network from the commit currently running on localnet to the provided git reference (commit, branch, tag).
// If the version of the commit we're upgrading to is the same as the version of the commit we're upgrading from, we bump the version by the specified level.
export async function upgradeNetwork(toGitRef: string, bumpByIfEqual: SemVerLevel = 'patch') {
const fromTomlVersion = await readPackageTomlVersion(path.dirname(process.cwd()));
console.log("Version we're upgrading from: " + fromTomlVersion);

// tmp/ is ignored in the bouncer .gitignore file.
const absoluteWorkspacePath = path.join(process.cwd(), 'tmp/upgrade-network');

console.log('Creating a new git workspace at: ' + absoluteWorkspacePath);

createGitWorkspaceAt(absoluteWorkspacePath, toGitRef);

const toTomlVersion = await readPackageTomlVersion(`${absoluteWorkspacePath}`);
console.log("Version we're upgrading to: " + toTomlVersion);

if (compareSemVer(fromTomlVersion, toTomlVersion) === 'greater') {
throw new Error(
"The version we're upgrading to is older than the version we're upgrading from. Ensure you selected the correct commits.",
);
}

if (fromTomlVersion === toTomlVersion) {
await bumpReleaseVersion(bumpByIfEqual, absoluteWorkspacePath);
}

const newToTomlVersion = await readPackageTomlVersion(path.join(absoluteWorkspacePath));
const isCompatible = isCompatibleWith(fromTomlVersion, newToTomlVersion);

if (isCompatible) {
// The CFE could be upgraded too. But an incompatible CFE upgrade would mean it's... incompatible, so covered in the other path.
console.log('The versions are compatible.');

// Runtime upgrade using the *new* version.
await simpleRuntimeUpgrade(absoluteWorkspacePath);
console.log('Upgrade complete.');
} else if (!isCompatible) {
// Incompatible upgrades requires running two versions of the CFEs side by side.
console.log('Incompatible CFE upgrades are not yet supported :(');
}

execSync(`cd ${absoluteWorkspacePath} && git worktree remove . --force`);
}
13 changes: 13 additions & 0 deletions bouncer/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,16 @@ export function isValidEthAddress(address: string): boolean {
const ethRegex = /^0x[a-fA-F0-9]{40}$/;
return ethRegex.test(address);
}

// "v1 is greater than v2" -> "greater"
export function compareSemVer(version1: string, version2: string) {
const v1 = version1.split('.').map(Number);
const v2 = version2.split('.').map(Number);

for (let i = 0; i < 3; i++) {
if (v1[i] > v2[i]) return 'greater';
if (v1[i] < v2[i]) return 'less';
}

return 'equal';
}
14 changes: 14 additions & 0 deletions bouncer/shared/utils/compile_binaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { execSync } from 'child_process';

// Returns the expected next version of the runtime.
export async function compileBinaries(type: 'runtime' | 'all', projectRoot: string) {
if (type === 'all') {
console.log('Building all the binaries...');
execSync(`cd ${projectRoot} && cargo build --release`);
} else {
console.log('Building the new runtime...');
execSync(`cd ${projectRoot}/state-chain/runtime && cargo build --release`);
}

console.log('Build complete.');
}

0 comments on commit 20e67c3

Please sign in to comment.