From c80bdb861a1b5b8a157c5a41f920b11755377332 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 5 Apr 2023 09:29:37 -0700 Subject: [PATCH] liveslots: add new test of durable collections across upgrades Check that the refcounts are maintained correctly --- .../test/test-collection-upgrade.js | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 packages/swingset-liveslots/test/test-collection-upgrade.js diff --git a/packages/swingset-liveslots/test/test-collection-upgrade.js b/packages/swingset-liveslots/test/test-collection-upgrade.js new file mode 100644 index 000000000000..25c5d52a15ec --- /dev/null +++ b/packages/swingset-liveslots/test/test-collection-upgrade.js @@ -0,0 +1,135 @@ +/* eslint-disable no-await-in-loop, @jessie.js/no-nested-await, no-shadow */ +import test from 'ava'; +import '@endo/init/debug.js'; + +import { Far } from '@endo/marshal'; +import { M } from '@agoric/store'; +import { makeLiveSlots } from '../src/liveslots.js'; +import { parseVatSlot } from '../src/parseVatSlots.js'; +import { kser } from './kmarshal.js'; +import { buildSyscall } from './liveslots-helpers.js'; +import { makeStartVat } from './util.js'; +import { makeMockGC } from './mock-gc.js'; + +test('durable collections survive upgrade', async t => { + let map1; + let set1; + let thing1; + let thing2; + let thing3; + + function build1(vatPowers, _vatParameters, baggage) { + const { VatData } = vatPowers; + const handle = VatData.makeKindHandle('thing'); + baggage.init('handle', handle); + const initData = name => ({ name }); + const how = { name: ({ state }) => state.name }; + const makeDurableThing = VatData.defineDurableKind(handle, initData, how); + thing1 = makeDurableThing('thing1'); + thing2 = makeDurableThing('thing2'); + thing3 = makeDurableThing('thing3'); + const valueShape = [thing1, M.any()]; + const durable = true; + map1 = VatData.makeScalarBigMapStore('map', { valueShape, durable }); + baggage.init('map', map1); + map1.init('string', harden([thing1, 'string'])); + map1.init('number', harden([thing1, 123])); + map1.init('thing2', harden([thing1, thing2])); + // thing3 is only used as the valueShape + set1 = VatData.makeScalarBigSetStore('set', { + valueShape: thing3, + durable, + }); + baggage.init('set', set1); + return Far('root', {}); + } + const make1 = () => ({ buildRootObject: build1 }); + + const kvStore = new Map(); + const { syscall: sc1 } = buildSyscall({ kvStore }); + const gcTools1 = makeMockGC(); + const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools1, undefined, make1); + + const startVat = makeStartVat(kser()); + await ls1.dispatch(startVat); + + const mapVref = ls1.testHooks.valToSlot.get(map1); + const setVref = ls1.testHooks.valToSlot.get(set1); + const thing1Vref = ls1.testHooks.valToSlot.get(thing1); + const thing2Vref = ls1.testHooks.valToSlot.get(thing2); + const thing3Vref = ls1.testHooks.valToSlot.get(thing3); + + // console.log(`-- map=${mapVref}, thing1=${thing1Vref}, thing2=${thing2Vref}`); + + // map and set should have refcount 1, from baggage + t.is(ls1.testHooks.getReachableRefCount(mapVref), 1); + t.is(ls1.testHooks.getReachableRefCount(setVref), 1); + // thing1 should have a refcount of 4: one for valueShape, plus one + // for each entry + t.is(ls1.testHooks.getReachableRefCount(thing1Vref), 4); + // thing2 should have 1, only the 'thing2' entry + t.is(ls1.testHooks.getReachableRefCount(thing2Vref), 1); + // thing3 should have 1, just the Set's valueShape + t.is(ls1.testHooks.getReachableRefCount(thing3Vref), 1); + + const collectionID = parseVatSlot(mapVref).subid; + const schemaKey = `vc.${collectionID}.|schemata`; + const schemataData = JSON.parse(kvStore.get(schemaKey)); + t.deepEqual(schemataData.slots, [thing1Vref]); + + // const compareEntriesByKey = ([ka], [kb]) => (ka < kb ? -1 : 1); + // t.log(Object.fromEntries([...kvStore.entries()].sort(compareEntriesByKey))); + + // Simulate upgrade by starting from the non-empty kvStore. + const clonedStore = new Map(kvStore); + + let map2; + let set2; + let newThing1; + let newThing2; + + function build2(vatPowers, _vatParameters, baggage) { + const { VatData } = vatPowers; + const handle = baggage.get('handle'); + const initData = name => ({ name }); + const how = { name: ({ state }) => state.name }; + VatData.defineDurableKind(handle, initData, how); + map2 = baggage.get('map'); + let s; + [newThing1, s] = map2.get('string'); + t.is(newThing1.name(), 'thing1'); + t.is(s, 'string'); + t.deepEqual(map2.get('string'), [newThing1, 'string']); + t.deepEqual(map2.get('number'), [newThing1, 123]); + newThing2 = map2.get('thing2')[1]; + t.is(newThing2.name(), 'thing2'); + set2 = baggage.get('set'); + return Far('root', {}); + } + const make2 = () => ({ buildRootObject: build2 }); + + const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore }); + const gcTools2 = makeMockGC(); + const ls2 = makeLiveSlots(sc2, 'vatA', {}, {}, gcTools2, undefined, make2); + + // restarting should work. this exercises valueShape being durable + // and being retrievable + await ls2.dispatch(startVat); + + // [...ls2.testHooks.slotToVal.entries()].map(([slot,wr]) => console.log(slot, wr.deref())); + + t.is(ls2.testHooks.slotToVal.get(thing1Vref).deref(), newThing1); + + // the vrefs should all still be the same + t.is(ls2.testHooks.valToSlot.get(map2), mapVref); + t.is(ls2.testHooks.valToSlot.get(set2), setVref); + t.is(ls2.testHooks.valToSlot.get(newThing1), thing1Vref); + t.is(ls2.testHooks.valToSlot.get(newThing2), thing2Vref); + + // refcounts should be the same + t.is(ls2.testHooks.getReachableRefCount(mapVref), 1); + t.is(ls1.testHooks.getReachableRefCount(setVref), 1); + t.is(ls2.testHooks.getReachableRefCount(thing1Vref), 4); + t.is(ls2.testHooks.getReachableRefCount(thing2Vref), 1); + t.is(ls2.testHooks.getReachableRefCount(thing3Vref), 1); +});