diff --git a/packages/agoric-cli/src/lib/wallet.js b/packages/agoric-cli/src/lib/wallet.js index d37188f9c5b..36c7d6d7bcc 100644 --- a/packages/agoric-cli/src/lib/wallet.js +++ b/packages/agoric-cli/src/lib/wallet.js @@ -112,6 +112,13 @@ export const coalesceWalletState = async (follower, invitationBrand) => { // values with oldest last const history = []; for await (const followerElement of iterateReverse(follower)) { + if ('error' in followerElement) { + console.error( + 'Skipping wallet update due to error:', + followerElement.error, + ); + continue; + } history.push(followerElement.value); } diff --git a/packages/casting/src/follower-cosmjs.js b/packages/casting/src/follower-cosmjs.js index 8a966fb4c1e..3ba1b59526b 100644 --- a/packages/casting/src/follower-cosmjs.js +++ b/packages/casting/src/follower-cosmjs.js @@ -330,11 +330,16 @@ export const makeCosmjsFollower = ( blockHeight, currentBlockHeight, ) => { - // AWAIT - const value = await /** @type {T} */ ( - unserializer ? E(unserializer).fromCapData(data) : data - ); - return { value, blockHeight, currentBlockHeight }; + await null; + try { + // AWAIT + const value = await /** @type {T} */ ( + unserializer ? E(unserializer).fromCapData(data) : data + ); + return { value, blockHeight, currentBlockHeight }; + } catch (e) { + return { blockHeight, currentBlockHeight, error: e, value: undefined }; + } }; /** diff --git a/packages/casting/src/types.js b/packages/casting/src/types.js index a746b16cd12..b789ae7a291 100644 --- a/packages/casting/src/types.js +++ b/packages/casting/src/types.js @@ -40,14 +40,17 @@ export {}; */ /** - * @see {ChangeFollower} - * @template T - * @typedef {object} ValueFollowerElement - * @property {T} value + * @typedef {object} ValueFollowerBase * @property {number} blockHeight * @property {number} currentBlockHeight */ +/** + * @see {ChangeFollower} + * @template T + * @typedef {ValueFollowerBase & ({ value: T } | { value: undefined, error: any })} ValueFollowerElement + */ + /** * @typedef {Pick, 'fromCapData' | 'unserialize'>} Unserializer */ diff --git a/packages/casting/test/test-mvp.js b/packages/casting/test/test-mvp.js index 6a7f051c27a..1dfffc138b2 100644 --- a/packages/casting/test/test-mvp.js +++ b/packages/casting/test/test-mvp.js @@ -1,5 +1,8 @@ // @ts-nocheck // eslint-disable-next-line import/order +import './lockdown.js'; + +import { makeMarshal } from '@endo/marshal'; import { test } from './prepare-test-env-ava.js'; import { @@ -167,6 +170,59 @@ test('unrecognized proof', async t => { ); }); +test('yields error on bad capdata without terminating', async t => { + const marshal = makeMarshal(); + const improperlyMarshalledData = { bad: 'data' }; + const properlyMarshalledData = { foo: 'bar' }; + const fakeValues = [ + improperlyMarshalledData, + marshal.toCapData(harden(properlyMarshalledData)), + ]; + t.plan(4); + const options = { batchSize: 1, marshaller: { toCapData: data => data } }; + const { controller, PORT } = await t.context.startFakeServer( + t, + fakeValues, + options, + ); + controller.advance(0); + /** @type {import('../src/types.js').LeaderOptions} */ + const lo = { + retryCallback: null, // fail fast, no retries + keepPolling: () => delay(1000).then(() => true), // poll really quickly + jitter: null, // no jitter + }; + /** @type {import('../src/types.js').FollowerOptions} */ + const so = { + proof: 'none', + }; + + const leader = makeLeader(`http://localhost:${PORT}/network-config`, lo); + const castingSpec = makeCastingSpec(':mailbox.agoric1foobarbaz'); + const follower = await makeFollower(castingSpec, leader, so); + let i = 0; + // eslint-disable-next-line no-unreachable-loop + for await (const { value, error } of iterateEach(follower)) { + if (i === 0) { + t.log(`value from follower, should be undefined:`, value); + t.log(`error from follower, should be defined:`, error); + + t.deepEqual(value, undefined); + t.assert(typeof error === 'object'); + + i += 1; + controller.advance(1); + } else if (i === 1) { + t.log(`value from follower, should be defined:`, value); + t.log(`error from follower, should be undefined:`, error); + + t.deepEqual(value, properlyMarshalledData); + t.deepEqual(error, undefined); + break; + } + } +}); + test.before(t => { t.context.cleanups = []; t.context.startFakeServer = startFakeServer;