Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 9 additions & 29 deletions src/services/modules/upgrade/upgrade-assert.services.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
import {isNullish, nonNullish} from '@dfinity/utils';
import {satelliteBuildType, type BuildType, type SatelliteParameters} from '@junobuild/admin';
import {gunzipFile, isGzip} from '@junobuild/cli-tools';
import {isNullish} from '@dfinity/utils';
import {satelliteBuildType, type SatelliteParameters} from '@junobuild/admin';
import {cyan, yellow} from 'kleur';
import type {AssertWasmModule, UpgradeWasm} from '../../../types/upgrade';
import {NEW_CMD_LINE, confirmAndExit} from '../../../utils/prompt.utils';

const wasmBuildType = async ({wasmModule}: AssertWasmModule): Promise<BuildType | undefined> => {
const buffer = Buffer.from(wasmModule);

const wasm = isGzip(buffer)
? await gunzipFile({
source: buffer
})
: buffer;

const mod = new WebAssembly.Module(wasm);

const metadata = WebAssembly.Module.customSections(mod, 'icp:public juno:build');

const decoder = new TextDecoder();
const buildType = decoder.decode(metadata[0]);

return nonNullish(buildType) && ['stock', 'extended'].includes(buildType)
? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(buildType as BuildType)
: undefined;
};
import {readWasmModuleMetadata} from '../../../utils/wasm.utils';

export const assertSatelliteBuildType = async ({
satellite,
Expand All @@ -36,8 +14,8 @@ export const assertSatelliteBuildType = async ({
const hideAgentJsConsoleWarn = globalThis.console.warn;
globalThis.console.warn = (): null => null;

const [wasmTypeResult, satelliteTypeResult] = await Promise.allSettled([
wasmBuildType({wasmModule}),
const [wasmMetadataResult, satelliteTypeResult] = await Promise.allSettled([
readWasmModuleMetadata({wasmModule}),
satelliteBuildType({
satellite
})
Expand All @@ -46,7 +24,7 @@ export const assertSatelliteBuildType = async ({
// Redo console.warn
globalThis.console.warn = hideAgentJsConsoleWarn;

if (wasmTypeResult.status === 'rejected') {
if (wasmMetadataResult.status === 'rejected') {
throw new Error(`The custom sections of the WASM module you try to upgrade cannot be read.`);
}

Expand All @@ -55,9 +33,11 @@ export const assertSatelliteBuildType = async ({
return;
}

const {value: wasmType} = wasmTypeResult;
const {value: wasmMetadata} = wasmMetadataResult;
const {value: satelliteType} = satelliteTypeResult;

const {buildType: wasmType} = wasmMetadata;

if (satelliteType === 'extended' && (wasmType === 'stock' || isNullish(wasmType))) {
await confirmAndExit(
`Your satellite is currently running on an ${cyan(
Expand Down
114 changes: 114 additions & 0 deletions src/utils/wasm.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {isNullish, nonNullish} from '@dfinity/utils';
import {type BuildType, findJunoPackageDependency} from '@junobuild/admin';
import {gunzipFile, isGzip} from '@junobuild/cli-tools';
import {JUNO_PACKAGE_SATELLITE_ID, type JunoPackage, JunoPackageSchema} from '@junobuild/config';
import {readFile} from 'node:fs/promises';
import {uint8ArrayToString} from 'uint8array-extras';

interface WasmMetadata {
gzipped: boolean;
junoPackage: JunoPackage | undefined;
buildType: BuildType | undefined;
}

export const readWasmFileMetadata = async ({path}: {path: string}): Promise<WasmMetadata> => {
const buffer = await readFile(path);
return await readWasmMetadata({buffer});
};

export const readWasmModuleMetadata = async ({
wasmModule
}: {
wasmModule: Uint8Array;
}): Promise<WasmMetadata> => {
const buffer = Buffer.from(wasmModule);
return await readWasmMetadata({buffer});
};

const readWasmMetadata = async ({buffer}: {buffer: Buffer}): Promise<WasmMetadata> => {
const gzipped = isGzip(buffer);

const wasm = gzipped
? await gunzipFile({
source: buffer
})
: buffer;

const junoPackage = await readCustomSectionJunoPackage({wasm});

const buildType = await extractBuildType({wasm, junoPackage});

return {
gzipped,
junoPackage,
buildType
};
};

const extractBuildType = async ({
junoPackage,
wasm
}: {
junoPackage: JunoPackage | undefined;
wasm: Buffer;
}): Promise<BuildType | undefined> => {
if (isNullish(junoPackage)) {
return await readDeprecatedBuildType({wasm});
}

const {name, dependencies} = junoPackage;

if (name === JUNO_PACKAGE_SATELLITE_ID) {
return 'stock';
}

const satelliteDependency = findJunoPackageDependency({
dependencies,
dependencyId: JUNO_PACKAGE_SATELLITE_ID
});

return nonNullish(satelliteDependency) ? 'extended' : undefined;
};

/**
* @deprecated Modern WASM build use JunoPackage.
*/
const readDeprecatedBuildType = async ({wasm}: {wasm: Buffer}): Promise<BuildType | undefined> => {
const buildType = await customSection({wasm, sectionName: 'icp:public juno:build'});

return nonNullish(buildType) && ['stock', 'extended'].includes(buildType)
? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(buildType as BuildType)
: undefined;
};

const readCustomSectionJunoPackage = async ({
wasm
}: {
wasm: Buffer;
}): Promise<JunoPackage | undefined> => {
const section = await customSection({wasm, sectionName: 'icp:public juno:package'});

if (isNullish(section)) {
return undefined;
}

const {success, data} = JunoPackageSchema.safeParse(section);
return success ? data : undefined;
};

const customSection = async ({
sectionName,
wasm
}: {
sectionName: string;
wasm: Buffer;
}): Promise<string | undefined> => {
const wasmModule = await WebAssembly.compile(wasm);

const pkgSections = WebAssembly.Module.customSections(wasmModule, sectionName);

const [pkgBuffer] = pkgSections;

return nonNullish(pkgBuffer) ? uint8ArrayToString(pkgBuffer) : undefined;
};