Skip to content

Commit

Permalink
feat(SwingSet): plumb consensusMode for stricter determinism
Browse files Browse the repository at this point in the history
This currently just disables `console.*` for vat users.  We hope
it will eventually be even stricter, but not yet.
  • Loading branch information
michaelfig committed Jul 14, 2021
1 parent 33ff03c commit 16ec7ca
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 55 deletions.
4 changes: 2 additions & 2 deletions packages/SwingSet/src/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | undefined>
* }} runtimeOptions
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions packages/SwingSet/src/kernel/loadVat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -97,6 +98,7 @@ export function makeVatLoader(stuff) {
];

const allowedStaticOptions = [
'consensusMode',
'description',
'name',
'vatParameters',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -198,6 +203,7 @@ export function makeVatLoader(stuff) {
isDynamic ? allowedDynamicOptions : allowedStaticOptions,
);
const {
consensusMode,
metered = isDynamic,
vatParameters = {},
managerType,
Expand Down Expand Up @@ -228,6 +234,7 @@ export function makeVatLoader(stuff) {
);

const managerOptions = {
consensusMode,
managerType,
bundle: vatSourceBundle,
metered,
Expand Down
7 changes: 5 additions & 2 deletions packages/SwingSet/src/kernel/state/vatKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions packages/SwingSet/src/kernel/vatManager/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function makeVatManagerFactory({

function validateManagerOptions(managerOptions) {
assertKnownOptions(managerOptions, [
'consensusMode',
'enablePipelining',
'managerType',
'gcEveryCrank',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function makeXsSubprocessFactory({
) {
parentLog(vatID, 'createFromBundle', { vatID });
const {
consensusMode,
vatParameters,
virtualObjectCacheSize,
enableDisavow,
Expand All @@ -60,6 +61,7 @@ export function makeXsSubprocessFactory({
metered,
compareSyscalls,
useTranscript,
vatConsole,
} = managerOptions;
assert(
!managerOptions.enableSetup,
Expand All @@ -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);
}
Expand Down Expand Up @@ -142,6 +144,7 @@ export function makeXsSubprocessFactory({
virtualObjectCacheSize,
enableDisavow,
enableVatstore,
consensusMode,
gcEveryCrank,
]);
if (bundleReply[0] === 'dispatchReady') {
Expand All @@ -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;
Expand Down
44 changes: 43 additions & 1 deletion packages/SwingSet/src/kernel/vatManager/supervisor-helper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-check
import { assert } from '@agoric/assert';
import { assert, details as X } from '@agoric/assert';
import {
insistVatSyscallObject,
insistVatSyscallResult,
Expand Down Expand Up @@ -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 };
16 changes: 6 additions & 10 deletions packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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?');
Expand All @@ -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?');
Expand All @@ -58,6 +50,7 @@ parentPort.on('message', ([type, ...margs]) => {
virtualObjectCacheSize,
enableDisavow,
enableVatstore,
consensusMode,
] = margs;

function testLog(...args) {
Expand Down Expand Up @@ -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,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { makeLiveSlots } from '../liveSlots.js';
import {
makeSupervisorDispatch,
makeSupervisorSyscall,
makeVatConsole,
} from './supervisor-helper.js';

// eslint-disable-next-line no-unused-vars
Expand All @@ -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();
Expand Down Expand Up @@ -76,6 +68,7 @@ fromParent.on('data', ([type, ...margs]) => {
virtualObjectCacheSize,
enableDisavow,
enableVatstore,
consensusMode,
] = margs;

function testLog(...args) {
Expand Down Expand Up @@ -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,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { makeLiveSlots } from '../liveSlots.js';
import {
makeSupervisorDispatch,
makeSupervisorSyscall,
makeVatConsole,
} from './supervisor-helper.js';

const encoder = new TextEncoder();
Expand Down Expand Up @@ -119,26 +120,8 @@ function makeWorker(port) {
/** @type { ((delivery: VatDeliveryObject) => Promise<VatDeliveryResult>) | 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
Expand All @@ -147,6 +130,7 @@ function makeWorker(port) {
* @param {unknown} virtualObjectCacheSize
* @param {boolean} enableDisavow
* @param {boolean} enableVatstore
* @param {boolean} consensusMode
* @param {boolean} [gcEveryCrank]
* @returns { Promise<Tagged> }
*/
Expand All @@ -157,6 +141,7 @@ function makeWorker(port) {
virtualObjectCacheSize,
enableDisavow,
enableVatstore,
consensusMode,
gcEveryCrank,
) {
/** @type { (vso: VatSyscallObject) => VatSyscallResult } */
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -238,20 +249,23 @@ 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],
args[2],
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);
}
Expand Down
Loading

0 comments on commit 16ec7ca

Please sign in to comment.