Skip to content

Commit

Permalink
fix(swing-store): replace getAllState/etc with a debug facet
Browse files Browse the repository at this point in the history
Previously, `getAllState` and `setAllState` were swing-store helper
methods which copy (or set) all the state of a store at once, used
exclusively for testing. These tests would either want to inspect the
swing-store contents directly, or clone a swing-store for e.g. replay
testing.

This commit replaces both with a new `debug` facet (a sibling of
`kernelStorage` and `hostStorage`), which offers two
methods.

`debug.dump()` returns a JS Object with the store data in an
easy-to-examine format (`dump.kvEntries['key']=value`,
`dump.streams[vatID]=[..]`, and `dump.snapshots[vatID] = {endPos,
hash, compressedSnapshot }`.

For cloning, `debug.serialize()` returns a Buffer that has a raw copy
of the SQLite backing store. This can be used to make a new DB by
passing it as an option to `initSwingStore`:

```js
const serialized = swingstore1.debug.serialize();
const swingstore2 = initSwingStore(null, { serialized });
```

Note that both `.serialize()` and `{ serialized }` require an in-RAM
database, rather than an on-disk one.

It also cleans up the streamstore types exports a bit.
  • Loading branch information
warner committed Jan 20, 2023
1 parent bdf9cc9 commit 886528c
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 287 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,14 @@ import { test } from '../../tools/prepare-test-env-ava.js';

// eslint-disable-next-line import/order
import { assert } from '@agoric/assert';
import { initSwingStore, getAllState } from '@agoric/swing-store';
import { initSwingStore } from '@agoric/swing-store';
import { initializeSwingset, makeSwingsetController } from '../../src/index.js';
import { kunser } from '../../src/lib/kmarshal.js';

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

// eslint-disable-next-line no-unused-vars
function dumpState(kernelStorage, vatID) {
const s = getAllState(kernelStorage).kvStuff;
const keys = Array.from(Object.keys(s)).sort();
for (const k of keys) {
if (k.startsWith(`${vatID}.vs.`)) {
console.log(k, s[k]);
}
}
}

async function testChangeParameters(t) {
const config = {
bootstrap: 'bootstrap',
Expand Down
6 changes: 3 additions & 3 deletions packages/SwingSet/test/devices/test-devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { test } from '../../tools/prepare-test-env-ava.js';

import bundleSource from '@endo/bundle-source';
import { initSwingStore, getAllState } from '@agoric/swing-store';
import { initSwingStore } from '@agoric/swing-store';
import { parse } from '@endo/marshal';

import {
Expand Down Expand Up @@ -211,7 +211,7 @@ test.serial('d2.5', async t => {
});

test.serial('device state', async t => {
const kernelStorage = initSwingStore().kernelStorage;
const { kernelStorage, debug } = initSwingStore();
const config = {
bootstrap: 'bootstrap',
vats: {
Expand All @@ -236,7 +236,7 @@ test.serial('device state', async t => {
const d3 = c1.deviceNameToID('d3');
await c1.run();
t.deepEqual(c1.dump().log, ['undefined', 'w+r', 'called', 'got {"s":"new"}']);
const s = getAllState(kernelStorage).kvStuff;
const s = debug.dump().kvEntries;
t.deepEqual(JSON.parse(s[`${d3}.deviceState`]), kser({ s: 'new' }));
t.deepEqual(JSON.parse(s[`${d3}.o.nextID`]), 10);
});
Expand Down
13 changes: 1 addition & 12 deletions packages/SwingSet/test/promise-watcher/test-promise-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,13 @@ import { test } from '../../tools/prepare-test-env-ava.js';
// eslint-disable-next-line import/order
import { assert } from '@agoric/assert';
// eslint-disable-next-line import/order
import { initSwingStore, getAllState } from '@agoric/swing-store';
import { initSwingStore } from '@agoric/swing-store';
import { initializeSwingset, makeSwingsetController } from '../../src/index.js';

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

// eslint-disable-next-line no-unused-vars
function dumpState(kernelStorage, vatID) {
const s = getAllState(kernelStorage).kvStuff;
const keys = Array.from(Object.keys(s)).sort();
for (const k of keys) {
if (k.startsWith(`${vatID}.vs.`)) {
console.log(k, s[k]);
}
}
}

async function testPromiseWatcher(t) {
const config = {
includeDevDependencies: true, // for vat-data
Expand Down
18 changes: 8 additions & 10 deletions packages/SwingSet/test/test-activityhash-vs-start.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { test } from '../tools/prepare-test-env-ava.js';

// eslint-disable-next-line import/order
import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store';
import { initSwingStore } from '@agoric/swing-store';
import { initializeSwingset, makeSwingsetController } from '../src/index.js';
import { buildTimer } from '../src/devices/timer/timer.js';

Expand Down Expand Up @@ -38,7 +38,7 @@ test.serial('restarting kernel does not change activityhash', async t => {
const deviceEndowments1 = {
timer: { ...timer1.endowments },
};
const ks1 = initSwingStore().kernelStorage;
const { kernelStorage: ks1, debug: debug1 } = initSwingStore();
// console.log(`--c1 build`);
await initializeSwingset(config, [], ks1);
const c1 = await makeSwingsetController(ks1, deviceEndowments1);
Expand All @@ -48,8 +48,8 @@ test.serial('restarting kernel does not change activityhash', async t => {
// console.log(`--c1 run1`);
await c1.run();

// console.log(`--c1 getAllState`);
const state = getAllState(ks1);
// console.log(`--c1 serialize`);
const serialized = debug1.serialize();
// console.log(`ah: ${c1.getActivityhash()}`);

// console.log(`--c1 poll1`);
Expand All @@ -70,8 +70,7 @@ test.serial('restarting kernel does not change activityhash', async t => {
const deviceEndowments2 = {
timer: { ...timer2.endowments },
};
const ks2 = initSwingStore().kernelStorage;
setAllState(ks2, state);
const { kernelStorage: ks2 } = initSwingStore(null, { serialized });
// console.log(`--c2 build`);
const c2 = await makeSwingsetController(ks2, deviceEndowments2);
// console.log(`ah: ${c2.getActivityhash()}`);
Expand Down Expand Up @@ -102,14 +101,14 @@ test.serial('comms initialize is deterministic', async t => {
const config = {};
config.bootstrap = 'bootstrap';
config.vats = { bootstrap: { sourceSpec } };
const ks1 = initSwingStore().kernelStorage;
const { kernelStorage: ks1, debug: debug1 } = initSwingStore();
await initializeSwingset(config, [], ks1);
const c1 = await makeSwingsetController(ks1, {});
c1.pinVatRoot('bootstrap');
// the bootstrap message will cause comms to initialize itself
await c1.run();

const state = getAllState(ks1);
const serialized = debug1.serialize();

// but the second message should not
c1.queueToVatRoot('bootstrap', 'addRemote', ['remote2']);
Expand All @@ -118,8 +117,7 @@ test.serial('comms initialize is deterministic', async t => {
await c1.shutdown();

// a kernel restart is loading a new kernel from the same state
const ks2 = initSwingStore().kernelStorage;
setAllState(ks2, state);
const { kernelStorage: ks2 } = initSwingStore(null, { serialized });
const c2 = await makeSwingsetController(ks2, {});

// the "am I already initialized?" check must be identical to the
Expand Down
67 changes: 28 additions & 39 deletions packages/SwingSet/test/test-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { test } from '../tools/prepare-test-env-ava.js';
// eslint-disable-next-line import/order
import { createHash } from 'crypto';
import { initSwingStore, getAllState, setAllState } from '@agoric/swing-store';
import { initSwingStore } from '@agoric/swing-store';
import makeKernelKeeper from '../src/kernel/state/kernelKeeper.js';
import { makeKernelStats } from '../src/kernel/state/stats.js';
import { KERNEL_STATS_METRICS } from '../src/kernel/metrics.js';
Expand All @@ -15,8 +15,8 @@ import {

const ignoredStateKeys = ['activityhash', 'kernelStats', 'local.kernelStats'];

function checkState(t, getState, expected) {
const state = getState();
function checkState(t, dump, expected) {
const state = dump().kvEntries;
const got = [];
for (const key of Object.getOwnPropertyNames(state)) {
if (!ignoredStateKeys.includes(key)) {
Expand All @@ -36,7 +36,7 @@ function checkState(t, getState, expected) {
t.deepEqual(got.sort(compareStrings), expected.sort(compareStrings));
}

async function testStorage(t, s, getState, commit) {
async function testStorage(t, s, dump, commit) {
t.falsy(s.has('missing'));
t.is(s.get('missing'), undefined);

Expand All @@ -59,28 +59,23 @@ async function testStorage(t, s, getState, commit) {
t.deepEqual(Array.from(s.getKeys('foo1', 'foo4')), ['foo1', 'foo3']);

if (commit) {
checkState(t, getState, []);
checkState(t, dump, []);
await commit();
}
checkState(t, getState, [
checkState(t, dump, [
['foo', 'f'],
['foo1', 'f1'],
['foo3', 'f3'],
]);
}

test('storageInMemory', async t => {
const kernelStorage = initSwingStore(null).kernelStorage;
await testStorage(
t,
kernelStorage.kvStore,
() => getAllState(kernelStorage).kvStuff,
null,
);
const { kernelStorage, debug } = initSwingStore(null);
await testStorage(t, kernelStorage.kvStore, debug.dump, null);
});

test('storage helpers', t => {
const kernelStorage = initSwingStore(null).kernelStorage;
const { kernelStorage, debug } = initSwingStore(null);
const kv = kernelStorage.kvStore;

kv.set('foo.0', 'f0');
Expand All @@ -89,7 +84,7 @@ test('storage helpers', t => {
kv.set('foo.3', 'f3');
// omit foo.4
kv.set('foo.5', 'f5');
checkState(t, () => getAllState(kernelStorage).kvStuff, [
checkState(t, debug.dump, [
['foo.0', 'f0'],
['foo.1', 'f1'],
['foo.2', 'f2'],
Expand All @@ -114,20 +109,20 @@ test('storage helpers', t => {
// zero, so if there is a gap in the key sequence (e.g., 'foo.4' in the
// above), they stop counting when they hit it
t.truthy(kv.has('foo.5'));
checkState(t, () => getAllState(kernelStorage).kvStuff, [['foo.5', 'f5']]);
checkState(t, debug.dump, [['foo.5', 'f5']]);
});

function buildKeeperStorageInMemory() {
const kernelStorage = initSwingStore(null).kernelStorage;
const { kernelStorage, debug } = initSwingStore(null);
return {
getState: () => getAllState(kernelStorage).kvStuff,
...debug, // serialize, dump
...kernelStorage,
};
}

function duplicateKeeper(getState) {
const kernelStorage = initSwingStore(null).kernelStorage;
setAllState(kernelStorage, { kvStuff: getState(), streamStuff: new Map() });
function duplicateKeeper(serialize) {
const serialized = serialize();
const { kernelStorage } = initSwingStore(null, { serialized });
const kernelKeeper = makeKernelKeeper(kernelStorage, null);
kernelKeeper.loadStats();
return kernelKeeper;
Expand All @@ -147,14 +142,13 @@ test('kernelStorage param guards', async t => {

test('kernel state', async t => {
const store = buildKeeperStorageInMemory();
const { getState } = store;
const k = makeKernelKeeper(store, null);
t.truthy(!k.getInitialized());
k.createStartingKernelState({ defaultManagerType: 'local' });
k.setInitialized();

k.emitCrankHashes();
checkState(t, getState, [
checkState(t, store.dump, [
['crankNumber', '0'],
['initialized', 'true'],
['gcActions', '[]'],
Expand All @@ -180,7 +174,6 @@ test('kernel state', async t => {

test('kernelKeeper vat names', async t => {
const store = buildKeeperStorageInMemory();
const { getState } = store;
const k = makeKernelKeeper(store, null);
k.createStartingKernelState({ defaultManagerType: 'local' });

Expand All @@ -190,7 +183,7 @@ test('kernelKeeper vat names', async t => {
t.is(v2, 'v2');

k.emitCrankHashes();
checkState(t, getState, [
checkState(t, store.dump, [
['crankNumber', '0'],
['gcActions', '[]'],
['runQueue', '[1,1]'],
Expand Down Expand Up @@ -220,7 +213,7 @@ test('kernelKeeper vat names', async t => {
t.is(k.getVatIDForName('Frank'), v2);
t.is(k.allocateVatIDForNameIfNeeded('Frank'), v2);

const k2 = duplicateKeeper(getState);
const k2 = duplicateKeeper(store.serialize);
t.deepEqual(k.getStaticVats(), [
['Frank', 'v2'],
['vatname5', 'v1'],
Expand All @@ -231,7 +224,6 @@ test('kernelKeeper vat names', async t => {

test('kernelKeeper device names', async t => {
const store = buildKeeperStorageInMemory();
const { getState } = store;
const k = makeKernelKeeper(store, null);
k.createStartingKernelState({ defaultManagerType: 'local' });

Expand All @@ -241,7 +233,7 @@ test('kernelKeeper device names', async t => {
t.is(d8, 'd8');

k.emitCrankHashes();
checkState(t, getState, [
checkState(t, store.dump, [
['crankNumber', '0'],
['gcActions', '[]'],
['runQueue', '[1,1]'],
Expand Down Expand Up @@ -271,7 +263,7 @@ test('kernelKeeper device names', async t => {
t.is(k.getDeviceIDForName('Frank'), d8);
t.is(k.allocateDeviceIDForNameIfNeeded('Frank'), d8);

const k2 = duplicateKeeper(getState);
const k2 = duplicateKeeper(store.serialize);
t.deepEqual(k.getDevices(), [
['Frank', 'd8'],
['devicename5', 'd7'],
Expand All @@ -282,7 +274,6 @@ test('kernelKeeper device names', async t => {

test('kernelKeeper runQueue', async t => {
const store = buildKeeperStorageInMemory();
const { getState } = store;
const k = makeKernelKeeper(store, null);
k.createStartingKernelState({ defaultManagerType: 'local' });

Expand All @@ -298,7 +289,7 @@ test('kernelKeeper runQueue', async t => {
t.is(k.getRunQueueLength(), 2);

k.emitCrankHashes();
const k2 = duplicateKeeper(getState);
const k2 = duplicateKeeper(store.serialize);

t.deepEqual(k.getNextRunQueueMsg(), { type: 'send', stuff: 'awesome' });
t.falsy(k.isRunQueueEmpty());
Expand All @@ -325,7 +316,6 @@ test('kernelKeeper runQueue', async t => {

test('kernelKeeper promises', async t => {
const store = buildKeeperStorageInMemory();
const { getState } = store;
const k = makeKernelKeeper(store, null);
k.createStartingKernelState({ defaultManagerType: 'local' });

Expand All @@ -342,7 +332,7 @@ test('kernelKeeper promises', async t => {
t.falsy(k.hasKernelPromise('kp99'));

k.emitCrankHashes();
let k2 = duplicateKeeper(getState);
let k2 = duplicateKeeper(store.serialize);

t.deepEqual(k2.getKernelPromise(p1), {
state: 'unresolved',
Expand All @@ -365,7 +355,7 @@ test('kernelKeeper promises', async t => {
});

k.emitCrankHashes();
k2 = duplicateKeeper(getState);
k2 = duplicateKeeper(store.serialize);
t.deepEqual(k2.getKernelPromise(p1), {
state: 'unresolved',
policy: 'ignore',
Expand Down Expand Up @@ -405,7 +395,7 @@ test('kernelKeeper promises', async t => {
expectedAcceptanceQueue.push({ type: 'send', target: 'kp40', msg: m2 });

k.emitCrankHashes();
k2 = duplicateKeeper(getState);
k2 = duplicateKeeper(store.serialize);
t.deepEqual(k2.getKernelPromise(p1).queue, [m1, m2]);

const ko = k.addKernelObject('v1');
Expand All @@ -423,7 +413,7 @@ test('kernelKeeper promises', async t => {
// all the subscriber/queue stuff should be gone
k.emitCrankHashes();

checkState(t, getState, [
checkState(t, store.dump, [
['crankNumber', '0'],
['device.nextID', '7'],
['vat.nextID', '1'],
Expand Down Expand Up @@ -491,7 +481,6 @@ test('kernelKeeper promise reject', async t => {

test('vatKeeper', async t => {
const store = buildKeeperStorageInMemory();
const { getState } = store;
const k = makeKernelKeeper(store, null);
k.createStartingKernelState({ defaultManagerType: 'local' });

Expand All @@ -509,7 +498,7 @@ test('vatKeeper', async t => {
t.is(vk.nextDeliveryNum(), 1n);

k.emitCrankHashes();
let vk2 = duplicateKeeper(getState).provideVatKeeper(v1);
let vk2 = duplicateKeeper(store.serialize).provideVatKeeper(v1);
t.is(vk2.mapVatSlotToKernelSlot(vatExport1), kernelExport1);
t.is(vk2.mapKernelSlotToVatSlot(kernelExport1), vatExport1);
t.is(vk2.nextDeliveryNum(), 2n);
Expand All @@ -522,7 +511,7 @@ test('vatKeeper', async t => {
t.is(vk.mapVatSlotToKernelSlot(vatImport2), kernelImport2);

k.emitCrankHashes();
vk2 = duplicateKeeper(getState).provideVatKeeper(v1);
vk2 = duplicateKeeper(store.serialize).provideVatKeeper(v1);
t.is(vk2.mapKernelSlotToVatSlot(kernelImport2), vatImport2);
t.is(vk2.mapVatSlotToKernelSlot(vatImport2), kernelImport2);
});
Expand Down
Loading

0 comments on commit 886528c

Please sign in to comment.