From 16ec7ca688465aa0ee3fb9ed08be5be910c2554f Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 2 Jul 2021 20:18:59 -0600 Subject: [PATCH] feat(SwingSet): plumb consensusMode for stricter determinism This currently just disables `console.*` for vat users. We hope it will eventually be even stricter, but not yet. --- packages/SwingSet/src/controller.js | 4 +- packages/SwingSet/src/kernel/loadVat.js | 7 +++ .../SwingSet/src/kernel/state/vatKeeper.js | 7 ++- .../SwingSet/src/kernel/vatManager/factory.js | 1 + .../vatManager/manager-subprocess-xsnap.js | 11 ++-- .../kernel/vatManager/supervisor-helper.js | 44 +++++++++++++- .../vatManager/supervisor-nodeworker.js | 16 ++--- .../vatManager/supervisor-subprocess-node.js | 17 +++--- .../vatManager/supervisor-subprocess-xsnap.js | 60 ++++++++++++------- .../src/kernel/vatManager/vat-warehouse.js | 7 ++- packages/SwingSet/src/types.js | 3 +- 11 files changed, 122 insertions(+), 55 deletions(-) diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index 5159bd7876e..5c8d449ba7d 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -123,7 +123,7 @@ export function makeStartXSnap(bundles, { snapStore, env, spawn }) { * slogCallbacks?: unknown, * slogFile?: string, * testTrackDecref?: unknown, - * warehousePolicy?: { maxVatsOnline?: number }, + * warehousePolicy?: { maxVatsOnline?: number, consensusMode?: boolean }, * spawn?: typeof import('child_process').spawn, * env?: Record * }} runtimeOptions @@ -420,7 +420,7 @@ export async function makeSwingsetController( * debugPrefix?: string, * slogCallbacks?: unknown, * testTrackDecref?: unknown, - * warehousePolicy?: { maxVatsOnline?: number }, + * warehousePolicy?: { maxVatsOnline?: number, consensusMode?: boolean }, * slogFile?: string, * }} runtimeOptions * @typedef { import('@agoric/swing-store-simple').KVStore } KVStore diff --git a/packages/SwingSet/src/kernel/loadVat.js b/packages/SwingSet/src/kernel/loadVat.js index 1e4959b226c..cb8b16e16bb 100644 --- a/packages/SwingSet/src/kernel/loadVat.js +++ b/packages/SwingSet/src/kernel/loadVat.js @@ -85,6 +85,7 @@ export function makeVatLoader(stuff) { } const allowedDynamicOptions = [ + 'consensusMode', 'description', 'metered', 'managerType', // TODO: not sure we want vats to be able to control this @@ -97,6 +98,7 @@ export function makeVatLoader(stuff) { ]; const allowedStaticOptions = [ + 'consensusMode', 'description', 'name', 'vatParameters', @@ -130,6 +132,9 @@ export function makeVatLoader(stuff) { * * @param {ManagerType} options.managerType * + * @param {boolean} options.consensusMode if true, + * turn off debugging features provided to a vat that may cause nondeterminism + * * @param {number} options.virtualObjectCacheSize * * @param {boolean} [options.metered] if true, @@ -198,6 +203,7 @@ export function makeVatLoader(stuff) { isDynamic ? allowedDynamicOptions : allowedStaticOptions, ); const { + consensusMode, metered = isDynamic, vatParameters = {}, managerType, @@ -228,6 +234,7 @@ export function makeVatLoader(stuff) { ); const managerOptions = { + consensusMode, managerType, bundle: vatSourceBundle, metered, diff --git a/packages/SwingSet/src/kernel/state/vatKeeper.js b/packages/SwingSet/src/kernel/state/vatKeeper.js index 87fdec7a8ee..dc34ffda455 100644 --- a/packages/SwingSet/src/kernel/state/vatKeeper.js +++ b/packages/SwingSet/src/kernel/state/vatKeeper.js @@ -95,10 +95,13 @@ export function makeVatKeeper( kvStore.set(`${vatID}.options`, JSON.stringify(options)); } - function getSourceAndOptions() { + /** + * @param {boolean} consensusMode + */ + function getSourceAndOptions(consensusMode) { const source = JSON.parse(kvStore.get(`${vatID}.source`)); const options = JSON.parse(kvStore.get(`${vatID}.options`)); - return harden({ source, options }); + return harden({ source, options: { ...options, consensusMode } }); } function getOptions() { diff --git a/packages/SwingSet/src/kernel/vatManager/factory.js b/packages/SwingSet/src/kernel/vatManager/factory.js index 9922d5fdc72..2e2e7cbee87 100644 --- a/packages/SwingSet/src/kernel/vatManager/factory.js +++ b/packages/SwingSet/src/kernel/vatManager/factory.js @@ -52,6 +52,7 @@ export function makeVatManagerFactory({ function validateManagerOptions(managerOptions) { assertKnownOptions(managerOptions, [ + 'consensusMode', 'enablePipelining', 'managerType', 'gcEveryCrank', diff --git a/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js index 6c9d0ed306e..6fe9fa53cd5 100644 --- a/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js @@ -51,6 +51,7 @@ export function makeXsSubprocessFactory({ ) { parentLog(vatID, 'createFromBundle', { vatID }); const { + consensusMode, vatParameters, virtualObjectCacheSize, enableDisavow, @@ -60,6 +61,7 @@ export function makeXsSubprocessFactory({ metered, compareSyscalls, useTranscript, + vatConsole, } = managerOptions; assert( !managerOptions.enableSetup, @@ -86,9 +88,9 @@ export function makeXsSubprocessFactory({ return mk.syscallFromWorker(vso); } case 'console': { - const [level, tag, ...rest] = args; - if (typeof level === 'string' && level in console) { - console[level](tag, ...rest); + const [level, ...rest] = args; + if (typeof level === 'string' && level in vatConsole) { + vatConsole[level](...rest); } else { console.error('bad console level', level); } @@ -142,6 +144,7 @@ export function makeXsSubprocessFactory({ virtualObjectCacheSize, enableDisavow, enableVatstore, + consensusMode, gcEveryCrank, ]); if (bundleReply[0] === 'dispatchReady') { @@ -160,7 +163,7 @@ export function makeXsSubprocessFactory({ parentLog(vatID, `sending delivery`, delivery); let result; try { - result = await issueTagged(['deliver', delivery]); + result = await issueTagged(['deliver', delivery, consensusMode]); } catch (err) { parentLog('issueTagged error:', err.code, err.message); let message; diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js b/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js index 0cc5ad3d890..b28203d7f9a 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js @@ -1,5 +1,5 @@ // @ts-check -import { assert } from '@agoric/assert'; +import { assert, details as X } from '@agoric/assert'; import { insistVatSyscallObject, insistVatSyscallResult, @@ -191,3 +191,45 @@ function makeSupervisorSyscall(syscallToManager, workerCanBlock) { harden(makeSupervisorSyscall); export { makeSupervisorSyscall }; + +/** + * Create a vat console, or a no-op if consensusMode is true. + * + * TODO: consider other methods per SES VirtualConsole. + * See https://github.com/Agoric/agoric-sdk/issues/2146 + * + * @param {Record<'debug' | 'log' | 'info' | 'warn' | 'error', (...args: any[]) => void>} logger + * the backing log method + * @param {boolean | ((logger: any, args: any[]) => void)} [wrapper] + */ +function makeVatConsole(logger, wrapper = true) { + const cons = Object.fromEntries( + ['debug', 'log', 'info', 'warn', 'error'].map(level => { + if (wrapper === false) { + // Static wrapper that never enables. We create unique no-op functions + // so that the different log methods cannot be compared to detect this + // mode. + return [level, () => {}]; + } + + const backingLog = logger[level]; + if (wrapper === true) { + // Static wrapper that always enables. + return [level, backingLog]; + } + + // Dynamic wrapper that may change its mind. + assert.typeof( + wrapper, + 'function', + X`Invalid VatConsole wrapper value ${wrapper}`, + ); + return [level, (...args) => wrapper(backingLog, args)]; + }), + ); + + return harden(cons); +} + +harden(makeVatConsole); +export { makeVatConsole }; diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js b/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js index 974dbb8ecf9..8ef2afb42d4 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js @@ -17,6 +17,7 @@ import { makeLiveSlots } from '../liveSlots.js'; import { makeSupervisorDispatch, makeSupervisorSyscall, + makeVatConsole, } from './supervisor-helper.js'; assert(parentPort, 'parentPort somehow missing, am I not a Worker?'); @@ -28,15 +29,6 @@ function workerLog(first, ...args) { workerLog(`supervisor started`); -function makeConsole(tag) { - const log = anylogger(tag); - const cons = {}; - for (const level of ['debug', 'log', 'info', 'warn', 'error']) { - cons[level] = log[level]; - } - return harden(cons); -} - function sendUplink(msg) { assert(msg instanceof Array, X`msg must be an Array`); assert(parentPort, 'parentPort somehow missing, am I not a Worker?'); @@ -58,6 +50,7 @@ parentPort.on('message', ([type, ...margs]) => { virtualObjectCacheSize, enableDisavow, enableVatstore, + consensusMode, ] = margs; function testLog(...args) { @@ -96,7 +89,10 @@ parentPort.on('message', ([type, ...margs]) => { const endowments = { ...ls.vatGlobals, - console: makeConsole(`SwingSet:vatWorker`), + console: makeVatConsole( + anylogger(`SwingSet:vat:${vatID}`), + !consensusMode, + ), assert, }; diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js index ca86df4b3d0..75dbfdf2791 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js @@ -23,6 +23,7 @@ import { makeLiveSlots } from '../liveSlots.js'; import { makeSupervisorDispatch, makeSupervisorSyscall, + makeVatConsole, } from './supervisor-helper.js'; // eslint-disable-next-line no-unused-vars @@ -32,15 +33,6 @@ function workerLog(first, ...args) { workerLog(`supervisor started`); -function makeConsole(tag) { - const log = anylogger(tag); - const cons = {}; - for (const level of ['debug', 'log', 'info', 'warn', 'error']) { - cons[level] = log[level]; - } - return harden(cons); -} - let dispatch; const toParent = arrayEncoderStream(); @@ -76,6 +68,7 @@ fromParent.on('data', ([type, ...margs]) => { virtualObjectCacheSize, enableDisavow, enableVatstore, + consensusMode, ] = margs; function testLog(...args) { @@ -110,9 +103,13 @@ fromParent.on('data', ([type, ...margs]) => { gcTools, ); + // Enable or disable the console accordingly. const endowments = { ...ls.vatGlobals, - console: makeConsole(`SwingSet:vatWorker`), + console: makeVatConsole( + anylogger(`SwingSet:vat:${vatID}`), + !consensusMode, + ), assert, }; diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js index 36784cb2858..1c20ba2e811 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js @@ -16,6 +16,7 @@ import { makeLiveSlots } from '../liveSlots.js'; import { makeSupervisorDispatch, makeSupervisorSyscall, + makeVatConsole, } from './supervisor-helper.js'; const encoder = new TextEncoder(); @@ -119,26 +120,8 @@ function makeWorker(port) { /** @type { ((delivery: VatDeliveryObject) => Promise) | null } */ let dispatch = null; - /** - * TODO: consider other methods per SES VirtualConsole. - * See https://github.com/Agoric/agoric-sdk/issues/2146 - * - * @param { string } tag - */ - function makeConsole(tag) { - const log = level => (...args) => { - const jsonSafeArgs = JSON.parse(`${q(args)}`); - port.send(['console', level, tag, ...jsonSafeArgs]); - }; - const cons = { - debug: log('debug'), - log: log('log'), - info: log('info'), - warn: log('warn'), - error: log('error'), - }; - return harden(cons); - } + /** @type {unknown} */ + let currentConsensusMode; /** * @param {unknown} vatID @@ -147,6 +130,7 @@ function makeWorker(port) { * @param {unknown} virtualObjectCacheSize * @param {boolean} enableDisavow * @param {boolean} enableVatstore + * @param {boolean} consensusMode * @param {boolean} [gcEveryCrank] * @returns { Promise } */ @@ -157,6 +141,7 @@ function makeWorker(port) { virtualObjectCacheSize, enableDisavow, enableVatstore, + consensusMode, gcEveryCrank, ) { /** @type { (vso: VatSyscallObject) => VatSyscallResult } */ @@ -208,9 +193,35 @@ function makeWorker(port) { gcTools, ); + const makeLog = level => { + return (...args) => { + // TODO: use more faithful stringification + const jsonSafeArgs = JSON.parse(`${q(args)}`); + port.send(['console', level, ...jsonSafeArgs]); + }; + }; + const logger = { + debug: makeLog('debug'), + log: makeLog('log'), + info: makeLog('info'), + warn: makeLog('warn'), + error: makeLog('error'), + }; + + // Enable or disable the console accordingly. + currentConsensusMode = consensusMode; const endowments = { ...ls.vatGlobals, - console: makeConsole(`SwingSet:vatWorker`), + console: makeVatConsole( + logger, + // We have to dynamically wrap the consensus mode so that it can change + // during the lifetime of the supervisor (which when snapshotting, is + // restored to its current heap across restarts, not actually stopping + // until the vat is terminated). + (fn, args) => { + currentConsensusMode || fn(...args); + }, + ), assert, // bootstrap provides HandledPromise HandledPromise: globalThis.HandledPromise, @@ -238,7 +249,8 @@ function makeWorker(port) { assert(!dispatch, 'cannot setBundle again'); const enableDisavow = !!args[4]; const enableVatstore = !!args[5]; - const gcEveryCrank = args[6] === undefined ? true : !!args[6]; + const consensusMode = !!args[6]; + const gcEveryCrank = args[7] === undefined ? true : !!args[7]; return setBundle( args[0], args[1], @@ -246,12 +258,14 @@ function makeWorker(port) { args[3], enableDisavow, enableVatstore, + consensusMode, gcEveryCrank, ); } case 'deliver': { assert(dispatch, 'cannot deliver before setBundle'); - const [vatDeliveryObject] = args; + const [vatDeliveryObject, consensusMode] = args; + currentConsensusMode = consensusMode; insistVatDeliveryObject(vatDeliveryObject); return dispatch(vatDeliveryObject); } diff --git a/packages/SwingSet/src/kernel/vatManager/vat-warehouse.js b/packages/SwingSet/src/kernel/vatManager/vat-warehouse.js index 1c2e738f74d..f172281b222 100644 --- a/packages/SwingSet/src/kernel/vatManager/vat-warehouse.js +++ b/packages/SwingSet/src/kernel/vatManager/vat-warehouse.js @@ -46,6 +46,7 @@ export const makeLRU = max => { * @param { KernelKeeper } kernelKeeper * @param { ReturnType } vatLoader * @param {{ + * consensusMode: boolean, * maxVatsOnline?: number, * snapshotInitial?: number, * snapshotInterval?: number, @@ -58,6 +59,8 @@ export const makeLRU = max => { */ export function makeVatWarehouse(kernelKeeper, vatLoader, policyOptions) { const { + // Whether to eliminate all vat nodeterminism. + consensusMode = false, maxVatsOnline = 50, // Often a large contract evaluation is among the first few deliveries, // so let's do a snapshot after just a few deliveries. @@ -108,7 +111,7 @@ export function makeVatWarehouse(kernelKeeper, vatLoader, policyOptions) { assert(kernelKeeper.vatIsAlive(vatID), X`${q(vatID)}: not alive`); const vatKeeper = kernelKeeper.provideVatKeeper(vatID); - const { source, options } = vatKeeper.getSourceAndOptions(); + const { source, options } = vatKeeper.getSourceAndOptions(consensusMode); const translators = provideTranslators(vatID); @@ -140,7 +143,7 @@ export function makeVatWarehouse(kernelKeeper, vatLoader, policyOptions) { enablePipelining, options, }; - console.log( + console.info( vatID, 'online:', options.managerType, diff --git a/packages/SwingSet/src/types.js b/packages/SwingSet/src/types.js index 9e581962c4a..44098183c18 100644 --- a/packages/SwingSet/src/types.js +++ b/packages/SwingSet/src/types.js @@ -23,6 +23,7 @@ * * @typedef { 'local' | 'nodeWorker' | 'node-subprocess' | 'xs-worker' | 'xs-worker-no-gc' } ManagerType * @typedef {{ + * consensusMode: boolean, * enablePipelining?: boolean, * managerType: ManagerType, * gcEveryCrank?: boolean, @@ -34,7 +35,7 @@ * virtualObjectCacheSize: number, * name: string, * compareSyscalls?: (originalSyscall: {}, newSyscall: {}) => Error | undefined, - * vatConsole: unknown, + * vatConsole: Console, * liveSlotsConsole?: Console, * } & (HasBundle | HasSetup)} ManagerOptions */