Skip to content

Commit

Permalink
fix: persistently track transcript start position for transcript cont…
Browse files Browse the repository at this point in the history
…inuity across upgrades

Closes #3293
  • Loading branch information
FUDCo committed May 5, 2022
1 parent a18226c commit 3c69350
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 9 deletions.
3 changes: 2 additions & 1 deletion packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ const enableKernelGC = true;
// $vatSlot is one of: o+$NN/o-$NN/p+$NN/p-$NN/d+$NN/d-$NN
// v$NN.c.$vatSlot = $kernelSlot = ko$NN/kp$NN/kd$NN
// v$NN.nextDeliveryNum = $NN
// v$NN.t.endPosition = $NN
// v$NN.t.startPosition = $NN // inclusive
// v$NN.t.endPosition = $NN // exclusive
// v$NN.vs.$key = string
// v$NN.meter = m$NN // XXX does this exist?
// v$NN.reapInterval = $NN or 'never'
Expand Down
24 changes: 17 additions & 7 deletions packages/SwingSet/src/kernel/state/vatKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export function initializeVatState(kvStore, streamStore, vatID) {
kvStore.set(`${vatID}.p.nextID`, `${FIRST_PROMISE_ID}`);
kvStore.set(`${vatID}.d.nextID`, `${FIRST_DEVICE_ID}`);
kvStore.set(`${vatID}.nextDeliveryNum`, `0`);
kvStore.set(
`${vatID}.t.startPosition`,
`${JSON.stringify(streamStore.STREAM_START)}`,
);
kvStore.set(
`${vatID}.t.endPosition`,
`${JSON.stringify(streamStore.STREAM_START)}`,
Expand Down Expand Up @@ -454,11 +458,14 @@ export function makeVatKeeper(
*
* @yields { TranscriptEntry } a stream of transcript entries
*/
function* getTranscript(startPos = streamStore.STREAM_START) {
function* getTranscript(startPos) {
if (startPos === undefined) {
startPos = JSON.parse(getRequired(`${vatID}.t.startPosition`));
}
const endPos = JSON.parse(getRequired(`${vatID}.t.endPosition`));
for (const entry of streamStore.readStream(
transcriptStream,
startPos,
/** @type { StreamPosition } */ (startPos),
endPos,
)) {
yield /** @type { TranscriptEntry } */ (JSON.parse(entry));
Expand Down Expand Up @@ -582,7 +589,6 @@ export function makeVatKeeper(

function removeSnapshotAndTranscript() {
const skey = `local.${vatID}.lastSnapshot`;
const epkey = `${vatID}.t.endPosition`;
if (snapStore) {
const notation = kvStore.get(skey);
if (notation) {
Expand All @@ -596,9 +602,9 @@ export function makeVatKeeper(
}
}
// TODO: same rollback concern
// TODO: streamStore.deleteStream(transcriptStream);
const newStart = streamStore.STREAM_START;
kvStore.set(epkey, `${JSON.stringify(newStart)}`);

const endPos = getRequired(`${vatID}.t.endPosition`);
kvStore.set(`${vatID}.t.startPosition`, endPos);
}

function vatStats() {
Expand All @@ -610,9 +616,13 @@ export function makeVatKeeper(
const objectCount = getCount(`${vatID}.o.nextID`, FIRST_OBJECT_ID);
const promiseCount = getCount(`${vatID}.p.nextID`, FIRST_PROMISE_ID);
const deviceCount = getCount(`${vatID}.d.nextID`, FIRST_DEVICE_ID);
const transcriptCount = JSON.parse(
const startCount = JSON.parse(
getRequired(`${vatID}.t.startPosition`),
).itemCount;
const endCount = JSON.parse(
getRequired(`${vatID}.t.endPosition`),
).itemCount;
const transcriptCount = endCount - startCount;

// TODO: Fix the downstream JSON.stringify to allow the counts to be BigInts
return harden({
Expand Down
38 changes: 38 additions & 0 deletions packages/SwingSet/test/upgrade/bootstrap-upgrade-replay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

export function buildRootObject() {
let vatAdmin;
let uptonRoot;
let uptonAdmin;

return Far('root', {
async bootstrap(vats, devices) {
vatAdmin = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin);
},

async buildV1() {
// build Upton, the upgrading vat
const bcap = await E(vatAdmin).getNamedBundleCap('upton');
const vatParameters = { version: 'v1' };
const options = { vatParameters };
const res = await E(vatAdmin).createVat(bcap, options);
uptonRoot = res.root;
uptonAdmin = res.adminNode;
return E(uptonRoot).phase1();
},

async upgradeV2() {
// upgrade Upton to version 2
const bcap = await E(vatAdmin).getNamedBundleCap('upton');
const vatParameters = { version: 'v2' };
await E(uptonAdmin).upgrade(bcap, vatParameters);
return E(uptonRoot).phase2();
},

async checkReplay() {
// ask Upton to do something after a restart
return E(uptonRoot).checkReplay();
},
});
}
84 changes: 84 additions & 0 deletions packages/SwingSet/test/upgrade/test-upgrade-replay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// eslint-disable-next-line import/order
import { test } from '../../tools/prepare-test-env-ava.js';

import { assert } from '@agoric/assert';
import { getAllState, setAllState } from '@agoric/swing-store';
import { provideHostStorage } from '../../src/controller/hostStorage.js';
import {
buildKernelBundles,
initializeSwingset,
makeSwingsetController,
} from '../../src/index.js';
import { capargs } from '../util.js';

function bfile(name) {
return new URL(name, import.meta.url).pathname;
}

function copy(data) {
return JSON.parse(JSON.stringify(data));
}

async function run(c, name, args = []) {
assert(Array.isArray(args));
const kpid = c.queueToVatRoot('bootstrap', name, capargs(args));
await c.run();
const status = c.kpStatus(kpid);
const capdata = c.kpResolution(kpid);
return [status, capdata];
}

test.before(async t => {
const kernelBundles = await buildKernelBundles();
t.context.data = { kernelBundles };
});

test('replay after upgrade', async t => {
const config = {
bootstrap: 'bootstrap',
vats: {
bootstrap: { sourceSpec: bfile('bootstrap-upgrade-replay.js') },
},
bundles: {
upton: { sourceSpec: bfile('vat-upton-replay.js') },
},
};

const hostStorage1 = provideHostStorage();
{
await initializeSwingset(copy(config), [], hostStorage1, {
kernelBundles: t.context.data.kernelBundles,
});
const c1 = await makeSwingsetController(hostStorage1);
c1.pinVatRoot('bootstrap');
await c1.run();

// create initial version
const [v1status, v1data] = await run(c1, 'buildV1');
t.is(v1status, 'fulfilled');
t.deepEqual(v1data, capargs(1));

// now perform the upgrade
const [v2status, v2data] = await run(c1, 'upgradeV2');
t.is(v2status, 'fulfilled');
// upgrade restart loses RAM state, hence adding 20 yields 20 rather than 21
t.deepEqual(v2data, capargs(20));
}

// copy the store just to be sure
const state1 = getAllState(hostStorage1);
const hostStorage2 = provideHostStorage();
setAllState(hostStorage2, state1);
{
const c2 = await makeSwingsetController(hostStorage2);
c2.pinVatRoot('bootstrap');
await c2.run();

// do something after replay that says we're still alive
const [rstatus, rdata] = await run(c2, 'checkReplay');
t.is(rstatus, 'fulfilled');

// replay retains RAM state of post-upgrade vat, hence adding 300 yields 320
t.deepEqual(rdata, capargs(320));
}
});
22 changes: 22 additions & 0 deletions packages/SwingSet/test/upgrade/vat-upton-replay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Far } from '@endo/marshal';

export function buildRootObject() {
let counter = 0;

return Far('root', {
phase1() {
counter += 1;
return counter;
},

phase2() {
counter += 20;
return counter;
},

checkReplay() {
counter += 300;
return counter;
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ test('dead vat state removed', async t => {
const kvStore = hostStorage.kvStore;
t.is(kvStore.get('vat.dynamicIDs'), '["v6"]');
t.is(kvStore.get('ko26.owner'), 'v6');
t.is(Array.from(kvStore.getKeys('v6.', 'v6/')).length, 32);
t.is(Array.from(kvStore.getKeys('v6.', 'v6/')).length, 33);

controller.queueToVatRoot('bootstrap', 'phase2', capargs([]));
await controller.run();
Expand Down

0 comments on commit 3c69350

Please sign in to comment.