From dea5483f719a8546738ecdab2a25314985471782 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 9 Apr 2021 13:05:09 -0500 Subject: [PATCH] feat: initializeSwingset snapshots XS supervisor - add snapstore to SwingStore API --- packages/SwingSet/src/controller.js | 4 --- packages/SwingSet/src/initializeSwingset.js | 36 ++++++++++++++++++- packages/SwingSet/src/types.js | 6 ++++ packages/SwingSet/test/test-controller.js | 25 +++++++++++++ packages/cosmic-swingset/lib/ag-solo/start.js | 17 ++++++++- 5 files changed, 82 insertions(+), 6 deletions(-) diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index 7b85f3fb73d..44dc5899258 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -395,9 +395,7 @@ export async function makeSwingsetController( * debugPrefix?: string, * slogCallbacks?: unknown, * testTrackDecref?: unknown, - * snapstorePath?: string, * }} runtimeOptions - * @typedef { import('@agoric/swing-store-simple').SwingStore } SwingStore */ export async function buildVatController( config, @@ -411,14 +409,12 @@ export async function buildVatController( debugPrefix, slogCallbacks, testTrackDecref, - snapstorePath, } = runtimeOptions; const actualRuntimeOptions = { verbose, debugPrefix, testTrackDecref, slogCallbacks, - snapstorePath, }; const initializationOptions = { verbose, kernelBundles }; let bootstrapResult; diff --git a/packages/SwingSet/src/initializeSwingset.js b/packages/SwingSet/src/initializeSwingset.js index f8d1ef3f0b9..9c7dd145362 100644 --- a/packages/SwingSet/src/initializeSwingset.js +++ b/packages/SwingSet/src/initializeSwingset.js @@ -2,11 +2,14 @@ // @ts-check import fs from 'fs'; import path from 'path'; +import { spawn } from 'child_process'; +import { type as osType } from 'os'; import { assert, details as X } from '@agoric/assert'; import bundleSource from '@agoric/bundle-source'; import { initSwingStore } from '@agoric/swing-store-simple'; +import { xsnap } from '@agoric/xsnap'; import { insistStorageAPI } from './storageAPI'; import { initializeKernel } from './kernel/initializeKernel'; @@ -214,6 +217,26 @@ export function swingsetIsInitialized(storage) { return !!storage.get('initialized'); } +/** + * @param {Record} bundles + * @param {ReturnType} snapstore + * @param {boolean} debug + * @typedef { any } BundleGE TODO: types for bundleSource + * @returns { Promise } hash of supervisor snapshot + */ +async function snapshotSupervisor(bundles, snapstore, debug) { + const worker = xsnap({ + handleCommand: async bytes => bytes, + spawn, + os: osType(), + debug, + }); + await worker.evaluate(`(${bundles.lockdown.source}\n)()`); + await worker.evaluate(`(${bundles.supervisor.source}\n)()`); + const supervisorHash = await snapstore.save(async fn => worker.snapshot(fn)); + return supervisorHash; +} + /** * @param {SwingSetConfig} config * @param {string[]} argv @@ -248,7 +271,9 @@ export async function initializeSwingset( } // Use ambient process.env only if caller did not specify. - const { env: { SWINGSET_WORKER_TYPE } = process.env } = runtimeOptions; + const { + env: { SWINGSET_WORKER_TYPE, XSNAP_DEBUG } = process.env, + } = runtimeOptions; const defaultManagerType = config.defaultManagerType || SWINGSET_WORKER_TYPE; switch (defaultManagerType) { case 'local': @@ -270,6 +295,15 @@ export async function initializeSwingset( hostStorage.set('lockdownBundle', JSON.stringify(kernelBundles.lockdown)); hostStorage.set('supervisorBundle', JSON.stringify(kernelBundles.supervisor)); + if (hostStorage.snapstore) { + const supervisorHash = await snapshotSupervisor( + kernelBundles, + hostStorage.snapstore, + !!XSNAP_DEBUG, + ); + hostStorage.set('supervisorHash', JSON.stringify(supervisorHash)); + } + if (config.bootstrap && argv) { const bootConfig = config.vats[config.bootstrap]; if (bootConfig) { diff --git a/packages/SwingSet/src/types.js b/packages/SwingSet/src/types.js index b1a0eb819cd..09b4729c2ff 100644 --- a/packages/SwingSet/src/types.js +++ b/packages/SwingSet/src/types.js @@ -90,3 +90,9 @@ * * Swingsets defined by scanning a directory in this manner define no devices. */ + +/** + * @typedef { import('@agoric/swing-store-simple').SwingStore & { + * snapstore?: ReturnType + * }} SwingStore + */ diff --git a/packages/SwingSet/test/test-controller.js b/packages/SwingSet/test/test-controller.js index 22b088eee0b..7bdf61a356f 100644 --- a/packages/SwingSet/test/test-controller.js +++ b/packages/SwingSet/test/test-controller.js @@ -3,7 +3,10 @@ import { test } from '../tools/prepare-test-env-ava'; import path from 'path'; +import fs from 'fs'; +import { tmpName } from 'tmp'; import { initSwingStore } from '@agoric/swing-store-simple'; +import { makeSnapstore } from '@agoric/xsnap'; import { buildVatController, loadBasedir } from '../src/index'; import { checkKT } from './util'; @@ -117,6 +120,28 @@ test('XS bootstrap', async t => { ); }); +test('SwingStore with snapstore', async t => { + const snapstorePath = path.resolve(__dirname, './fixture-xs-snapshots/'); + fs.mkdirSync(snapstorePath, { recursive: true }); + + const kvStorage = initSwingStore().storage; + + const snapstore = makeSnapstore(snapstorePath, { + tmpName, + existsSync: fs.existsSync, + createReadStream: fs.createReadStream, + createWriteStream: fs.createWriteStream, + rename: fs.promises.rename, + unlink: fs.promises.unlink, + resolve: path.resolve, + }); + const hostStorage = { snapstore, ...kvStorage }; + await buildVatController({}, [], { hostStorage }); + const supervisorHash = hostStorage.get('supervisorHash'); + t.is(typeof supervisorHash, 'string', 'supervisorHash is available'); + t.is(supervisorHash.length, 66, 'supervisorHash is available'); +}); + test('validate config.defaultManagerType', async t => { const config = await loadBasedir( path.resolve(__dirname, 'basedir-controller-2'), diff --git a/packages/cosmic-swingset/lib/ag-solo/start.js b/packages/cosmic-swingset/lib/ag-solo/start.js index 7eb8694044e..c11aa7a7bcb 100644 --- a/packages/cosmic-swingset/lib/ag-solo/start.js +++ b/packages/cosmic-swingset/lib/ag-solo/start.js @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import temp from 'temp'; +import { tmpName } from 'tmp'; // TODO: reconcile tmp vs. temp import { fork } from 'child_process'; import { promisify } from 'util'; // import { createHash } from 'crypto'; @@ -24,6 +25,7 @@ import { buildPlugin, buildTimer, } from '@agoric/swingset-vat'; +import { makeSnapstore } from '@agoric/xsnap'; import { getBestSwingStore } from '../check-lmdb'; import { deliver, addDeliveryTarget } from './outbound'; @@ -141,7 +143,20 @@ async function buildSwingset( const tempdir = path.resolve(kernelStateDBDir, 'check-lmdb-tempdir'); const { openSwingStore } = getBestSwingStore(tempdir); - const { storage, commit } = openSwingStore(kernelStateDBDir); + const { storage: storage0, commit } = openSwingStore(kernelStateDBDir); + const snapstore = makeSnapstore( + path.resolve(kernelStateDBDir, 'xs-snapshots'), + { + tmpName, + existsSync: fs.existsSync, + createReadStream: fs.createReadStream, + createWriteStream: fs.createWriteStream, + rename: fs.promises.rename, + unlink: fs.promises.unlink, + resolve: path.resolve, + }, + ); + const storage = { snapstore, ...storage0 }; if (!swingsetIsInitialized(storage)) { if (defaultManagerType && !config.defaultManagerType) {