From 81f19d17c384946cdb2e39ddad755b26f8a9f92c Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 30 Sep 2024 02:23:02 +0000 Subject: [PATCH 1/5] test(vow): watchers are remotable --- packages/vow/test/disconnect.test.js | 30 ++++++++++++++++----------- packages/vow/test/watch-utils.test.js | 4 ++-- packages/vow/test/watch.test.js | 27 +++++++++++++----------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/vow/test/disconnect.test.js b/packages/vow/test/disconnect.test.js index 5ea0e73e4c3..8b4ce81e4d7 100644 --- a/packages/vow/test/disconnect.test.js +++ b/packages/vow/test/disconnect.test.js @@ -43,6 +43,23 @@ test('retry on disconnection', async t => { }, }, ); + const makeFinalWatcher = zone.exoClass( + 'FinalWatcher', + undefined, + final => ({ final }), + { + onFulfilled(value) { + t.is(this.state.final, 'happy'); + t.is(value, 'resolved'); + return value; + }, + onRejected(reason) { + t.is(this.state.final, 'sad'); + t.is(reason && reason.message, 'dejected'); + return ['rejected', reason]; + }, + }, + ); const PLANS = [ [0, 'happy'], @@ -72,18 +89,7 @@ test('retry on disconnection', async t => { let resultP; switch (pattern) { case 'watch-with-handler': { - const resultW = watch(vow, { - onFulfilled(value) { - t.is(plan[final], 'happy'); - t.is(value, 'resolved'); - return value; - }, - onRejected(reason) { - t.is(plan[final], 'sad'); - t.is(reason && reason.message, 'dejected'); - return ['rejected', reason]; - }, - }); + const resultW = watch(vow, makeFinalWatcher(plan[final])); t.is('then' in resultW, false, 'watch resultW.then is undefined'); resultP = when(resultW); break; diff --git a/packages/vow/test/watch-utils.test.js b/packages/vow/test/watch-utils.test.js index 4a622f37195..54fc8574b5a 100644 --- a/packages/vow/test/watch-utils.test.js +++ b/packages/vow/test/watch-utils.test.js @@ -256,14 +256,14 @@ test('asPromise handles watcher arguments', async t => { const vow = watch(testPromiseP); let watcherCalled = false; - const watcher = { + const watcher = zone.exo('Watcher', undefined, { onFulfilled(value, ctx) { watcherCalled = true; t.is(value, 'watcher test'); t.deepEqual(ctx, ['ctx']); return value; }, - }; + }); // XXX fix type: `watcherContext` doesn't need to be an array const result = await asPromise(vow, watcher, ['ctx']); diff --git a/packages/vow/test/watch.test.js b/packages/vow/test/watch.test.js index 61ccec95a9b..1f4e4437276 100644 --- a/packages/vow/test/watch.test.js +++ b/packages/vow/test/watch.test.js @@ -232,18 +232,21 @@ test('disconnection of non-vow informs watcher', async t => { // Even though this promise is rejected with a retryable reason, there's no // vow before it to retry, so we pass the rejection up to the watcher. - /* eslint-disable-next-line prefer-promise-reject-errors */ - const vow = watch(Promise.reject('disconnected'), { - onFulfilled(value) { - t.log(`onfulfilled ${value}`); - t.fail('should not fulfil'); - return 'fulfilled'; - }, - onRejected(reason) { - t.is(reason, 'disconnected'); - return `rejected ${reason}`; - }, - }); + const vow = watch( + /* eslint-disable-next-line prefer-promise-reject-errors */ + Promise.reject('disconnected'), + zone.exo('Watcher', undefined, { + onFulfilled(value) { + t.log(`onfulfilled ${value}`); + t.fail('should not fulfil'); + return 'fulfilled'; + }, + onRejected(reason) { + t.is(reason, 'disconnected'); + return `rejected ${reason}`; + }, + }), + ); t.is(await when(vow), 'rejected disconnected'); }); From 11eddefd2e7a3cface7215b7a8f6645f26eeee4f Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 30 Sep 2024 02:23:35 +0000 Subject: [PATCH 2/5] test(internal): callbacks are remotable --- packages/internal/src/storage-test-utils.js | 142 ++++++++++---------- packages/internal/test/callback.test.js | 4 +- 2 files changed, 75 insertions(+), 71 deletions(-) diff --git a/packages/internal/src/storage-test-utils.js b/packages/internal/src/storage-test-utils.js index b1ce51b2cdc..f224c47ad93 100644 --- a/packages/internal/src/storage-test-utils.js +++ b/packages/internal/src/storage-test-utils.js @@ -108,82 +108,86 @@ export const makeFakeStorageKit = (rootPath, rootOptions) => { }; /** @type {StorageMessage[]} */ const messages = []; - /** @param {StorageMessage} message */ - const toStorage = message => { - messages.push(message); - switch (message.method) { - case 'getStoreKey': { - const [key] = message.args; - return { storeName: 'swingset', storeSubkey: `fake:${key}` }; - } - case 'get': { - const [key] = message.args; - return data.has(key) ? data.get(key) : null; - } - case 'children': { - const [key] = message.args; - const childEntries = getChildEntries(`${key}.`); - return [...childEntries.keys()]; - } - case 'entries': { - const [key] = message.args; - const childEntries = getChildEntries(`${key}.`); - return [...childEntries.entries()].map(entry => - entry[1] != null ? entry : [entry[0]], - ); - } - case 'set': - case 'setWithoutNotify': { - trace('toStorage set', message); - /** @type {StorageEntry[]} */ - const newEntries = message.args; - for (const [key, value] of newEntries) { - if (value != null) { - data.set(key, value); - } else { - data.delete(key); - } + const toStorage = Far( + 'ToStorage', + /** @param {StorageMessage} message */ + message => { + messages.push(message); + switch (message.method) { + case 'getStoreKey': { + const [key] = message.args; + return { storeName: 'swingset', storeSubkey: `fake:${key}` }; } - break; - } - case 'append': { - trace('toStorage append', message); - /** @type {StorageEntry[]} */ - const newEntries = message.args; - for (const [key, value] of newEntries) { - value != null || Fail`attempt to append with no value`; - // In the absence of block boundaries, everything goes in a single StreamCell. - const oldVal = data.get(key); - let streamCell; - if (oldVal != null) { - try { - streamCell = JSON.parse(oldVal); - assert(isStreamCell(streamCell)); - } catch (_err) { - streamCell = undefined; + case 'get': { + const [key] = message.args; + return data.has(key) ? data.get(key) : null; + } + case 'children': { + const [key] = message.args; + const childEntries = getChildEntries(`${key}.`); + return [...childEntries.keys()]; + } + case 'entries': { + const [key] = message.args; + const childEntries = getChildEntries(`${key}.`); + return [...childEntries.entries()].map(entry => + entry[1] != null ? entry : [entry[0]], + ); + } + case 'set': + case 'setWithoutNotify': { + trace('toStorage set', message); + /** @type {StorageEntry[]} */ + const newEntries = message.args; + for (const [key, value] of newEntries) { + if (value != null) { + data.set(key, value); + } else { + data.delete(key); } } - if (streamCell === undefined) { - streamCell = { - blockHeight: '0', - values: oldVal != null ? [oldVal] : [], - }; + break; + } + case 'append': { + trace('toStorage append', message); + /** @type {StorageEntry[]} */ + const newEntries = message.args; + for (const [key, value] of newEntries) { + value != null || Fail`attempt to append with no value`; + // In the absence of block boundaries, everything goes in a single StreamCell. + const oldVal = data.get(key); + let streamCell; + if (oldVal != null) { + try { + streamCell = JSON.parse(oldVal); + assert(isStreamCell(streamCell)); + } catch (_err) { + streamCell = undefined; + } + } + if (streamCell === undefined) { + streamCell = { + blockHeight: '0', + values: oldVal != null ? [oldVal] : [], + }; + } + streamCell.values.push(value); + data.set(key, JSON.stringify(streamCell)); } - streamCell.values.push(value); - data.set(key, JSON.stringify(streamCell)); + break; } - break; + case 'size': + // Intentionally incorrect because it counts non-child descendants, + // but nevertheless supports a "has children" test. + return [...data.keys()].filter(k => + k.startsWith(`${message.args[0]}.`), + ).length; + default: + throw Error(`unsupported method: ${message.method}`); } - case 'size': - // Intentionally incorrect because it counts non-child descendants, - // but nevertheless supports a "has children" test. - return [...data.keys()].filter(k => k.startsWith(`${message.args[0]}.`)) - .length; - default: - throw Error(`unsupported method: ${message.method}`); - } - }; + }, + ); const rootNode = makeChainStorageRoot(toStorage, rootPath, resolvedOptions); return { rootNode, diff --git a/packages/internal/test/callback.test.js b/packages/internal/test/callback.test.js index db0f09ef887..c79e8fadb4d 100644 --- a/packages/internal/test/callback.test.js +++ b/packages/internal/test/callback.test.js @@ -304,11 +304,11 @@ test('makeAttenuator', async t => { overrides: { m1: null, m2: cb.makeMethodCallback( - { + Far('Abc', { abc() { return 'return abc'; }, - }, + }), 'abc', ), }, From fbde033d76ca19a50b0cc6938b12718f610584c3 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 30 Sep 2024 03:17:36 +0000 Subject: [PATCH 3/5] test(cache): storage callback is remotable --- packages/cache/test/storage.test.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/cache/test/storage.test.js b/packages/cache/test/storage.test.js index af41dabf3ae..88363ff6c14 100644 --- a/packages/cache/test/storage.test.js +++ b/packages/cache/test/storage.test.js @@ -27,13 +27,16 @@ harden(makeSimpleMarshaller); const setup = () => { const storageNodeState = {}; - const chainStorage = makeChainStorageRoot(message => { - assert(message.method === 'set'); - assert(message.args.length === 1); - const [[path, value]] = message.args; - assert(path === 'cache'); - storageNodeState.cache = value; - }, 'cache'); + const chainStorage = makeChainStorageRoot( + Far('ToStorage', message => { + assert(message.method === 'set'); + assert(message.args.length === 1); + const [[path, value]] = message.args; + assert(path === 'cache'); + storageNodeState.cache = value; + }), + 'cache', + ); const cache = makeCache( makeChainStorageCoordinator(chainStorage, makeSimpleMarshaller()), ); From d4afab86b6a75860b10c22019449ca28035e710d Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 30 Sep 2024 03:21:34 +0000 Subject: [PATCH 4/5] fix(internal): null chain storage uses remotable messenger --- packages/internal/src/lib-chainStorage.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/internal/src/lib-chainStorage.js b/packages/internal/src/lib-chainStorage.js index e67b5766166..672820deb05 100644 --- a/packages/internal/src/lib-chainStorage.js +++ b/packages/internal/src/lib-chainStorage.js @@ -1,7 +1,7 @@ // @ts-check import { Fail } from '@endo/errors'; -import { E } from '@endo/far'; +import { E, Far } from '@endo/far'; import { M } from '@endo/patterns'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; import * as cb from './callback.js'; @@ -278,7 +278,10 @@ export function makeChainStorageRoot( */ const makeNullStorageNode = () => { // XXX re-use "ChainStorage" methods above which don't actually depend on chains - return makeChainStorageRoot(() => null, 'null'); + return makeChainStorageRoot( + Far('NullMessenger', () => null), + 'null', + ); }; /** From 4f07d1c82709b4572cc5e883679a30d57c827803 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 30 Sep 2024 03:57:14 +0000 Subject: [PATCH 5/5] fix(cosmic-swingset): installation publisher uses remotable storage messenger --- packages/cosmic-swingset/src/chain-main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cosmic-swingset/src/chain-main.js b/packages/cosmic-swingset/src/chain-main.js index 83f38f2aa6a..10b669da82a 100644 --- a/packages/cosmic-swingset/src/chain-main.js +++ b/packages/cosmic-swingset/src/chain-main.js @@ -11,7 +11,7 @@ import { resolve as importMetaResolve } from 'import-meta-resolve'; import tmp from 'tmp'; import { Fail, q } from '@endo/errors'; -import { E } from '@endo/far'; +import { E, Far } from '@endo/far'; import { makeMarshal } from '@endo/marshal'; import { isNat } from '@endo/nat'; import { M, mustMatch } from '@endo/patterns'; @@ -432,9 +432,9 @@ export default async function main(progname, args, { env, homedir, agcc }) { } } - const toStorage = message => { + const toStorage = Far('BridgeStorageHandler', message => { return doOutboundBridge(BridgeId.STORAGE, message); - }; + }); const makeInstallationPublisher = () => { const installationStorageNode = makeChainStorageRoot(