From 622ed61b708b2177dc398284816d8f7a0861b064 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 21 Jun 2024 18:10:16 -0400 Subject: [PATCH] feat(findBrandInVBank): add optional parameter to invalidate cache --- .../src/examples/sendAnywhere.contract.js | 4 +- .../src/exos/agoric-names-tools.js | 61 +++++++++++-------- .../test/exos/agoric-names-tools.test.ts | 46 +++++++++----- 3 files changed, 70 insertions(+), 41 deletions(-) diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index 081af6dad12..d4af19e20e2 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -71,8 +71,8 @@ export const start = async (zcf, privateArgs, baggage) => { const { chainName, destAddr } = offerArgs; const { give } = seat.getProposal(); const [[kw, amt]] = entries(give); - // TODO remove V.when() when integrating with asyncFlow - const { denom } = await V.when( + // XXX when() until membrane + const { denom } = await E.when( agoricNamesTools.findBrandInVBank(amt.brand), ); const chain = await orch.getChain(chainName); diff --git a/packages/orchestration/src/exos/agoric-names-tools.js b/packages/orchestration/src/exos/agoric-names-tools.js index 4e4b8aed9d9..cc36ee065f5 100644 --- a/packages/orchestration/src/exos/agoric-names-tools.js +++ b/packages/orchestration/src/exos/agoric-names-tools.js @@ -17,7 +17,9 @@ const { Fail } = assert; * Perform remote calls to agoricNames in membrane-friendly way. This is an * interim approach until https://github.com/Agoric/agoric-sdk/issues/9541, * https://github.com/Agoric/agoric-sdk/pull/9322, or - * https://github.com/Agoric/agoric-sdk/pull/9519 + * https://github.com/Agoric/agoric-sdk/pull/9519. + * + * XXX only works once per zone. * * XXX consider exposing `has`, `entries`, `keys`, `values` from `NameHub` * @@ -26,14 +28,16 @@ const { Fail } = assert; */ export const makeResumableAgoricNamesHack = ( zone, - { agoricNames, vowTools: { watch } }, + { agoricNames, vowTools: { watch, asVow } }, ) => { const makeResumableAgoricNamesHackKit = zone.exoClassKit( 'ResumableAgoricNamesHack', { public: M.interface('ResumableAgoricNamesHackI', { lookup: M.call().rest(M.arrayOf(M.string())).returns(VowShape), - findBrandInVBank: M.call(BrandShape).returns(VowShape), + findBrandInVBank: M.call(BrandShape) + .optional(M.boolean()) + .returns(VowShape), }), vbankAssetEntriesWatcher: M.interface('vbankAssetEntriesWatcher', { onFulfilled: M.call(M.arrayOf(M.record())) @@ -54,16 +58,15 @@ export const makeResumableAgoricNamesHack = ( * @param {{ brand: Brand<'nat'> }} ctx */ onFulfilled(assets, { brand }) { - const { vbankAssetsByBrand } = this.state; - vbankAssetsByBrand.addAll(makeCopyMap(assets.map(a => [a.brand, a]))); - if (!vbankAssetsByBrand.has(brand)) { - return watch( - Promise.reject( - Fail`brand ${brand} not in agoricNames.vbankAsset`, - ), + return asVow(() => { + const { vbankAssetsByBrand } = this.state; + vbankAssetsByBrand.addAll( + makeCopyMap(assets.map(a => [a.brand, a])), ); - } - return watch(vbankAssetsByBrand.get(brand)); + vbankAssetsByBrand.has(brand) || + Fail`brand ${brand} not in agoricNames.vbankAsset`; + return vbankAssetsByBrand.get(brand); + }); }, }, public: { @@ -72,25 +75,35 @@ export const makeResumableAgoricNamesHack = ( return watch(E(agoricNames).lookup(...args)); }, /** + * Look up asset info, like denom, in agoricNames.vbankAsset using a + * Brand. + * + * Caches the query to agoricNames in the first call. Subsequent lookups + * are via cache unless a refetch is specified or a brand is not found. + * * @param {Brand<'nat'>} brand + * @param {boolean} [refetch] if true, will invalidate the cache * @returns {Vow} */ - findBrandInVBank(brand) { - const { vbankAssetsByBrand } = this.state; - if (vbankAssetsByBrand.has(brand)) { - return watch(vbankAssetsByBrand.get(brand)); - } - const vbankAssetNameHubP = E(agoricNames).lookup('vbankAsset'); - const vbankAssetEntriesP = E(vbankAssetNameHubP).values(); - return watch( - vbankAssetEntriesP, - this.facets.vbankAssetEntriesWatcher, - { brand }, - ); + findBrandInVBank(brand, refetch) { + return asVow(() => { + const { vbankAssetsByBrand } = this.state; + if (vbankAssetsByBrand.has(brand) && !refetch) { + return vbankAssetsByBrand.get(brand); + } + const vbankAssetNameHubP = E(agoricNames).lookup('vbankAsset'); + const vbankAssetEntriesP = E(vbankAssetNameHubP).values(); + return watch( + vbankAssetEntriesP, + this.facets.vbankAssetEntriesWatcher, + { brand }, + ); + }); }, }, }, ); + // XXX only works once per zone. return makeResumableAgoricNamesHackKit().public; }; /** @typedef {ReturnType} AgNamesTools */ diff --git a/packages/orchestration/test/exos/agoric-names-tools.test.ts b/packages/orchestration/test/exos/agoric-names-tools.test.ts index 58577bacb24..924a72591a7 100644 --- a/packages/orchestration/test/exos/agoric-names-tools.test.ts +++ b/packages/orchestration/test/exos/agoric-names-tools.test.ts @@ -1,10 +1,10 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { E } from '@endo/far'; -import { V } from '@agoric/vow/vat.js'; +import { heapVowE as E } from '@agoric/vow/vat.js'; import { makeHeapZone } from '@agoric/zone'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; import { makeIssuerKit } from '@agoric/ertp'; +import { AssetInfo } from '@agoric/vats/src/vat-bank.js'; import { makeResumableAgoricNamesHack } from '../../src/exos/agoric-names-tools.js'; import { commonSetup } from '../supports.js'; @@ -20,33 +20,49 @@ test('agoric names tools', async t => { vowTools, }); - const chainEntry = await V.when(agNamesTools.lookup('chain', 'celestia')); + const chainEntry = await E.when(agNamesTools.lookup('chain', 'celestia')); t.like(chainEntry, { chainId: 'celestia' }); - const istDenom = await V.when(agNamesTools.findBrandInVBank(ist.brand)); + const istDenom = await E.when(agNamesTools.findBrandInVBank(ist.brand)); t.like(istDenom, { denom: 'uist' }); const moolah = withAmountUtils(makeIssuerKit('MOO')); - await t.throwsAsync(V.when(agNamesTools.findBrandInVBank(moolah.brand)), { + await t.throwsAsync(E.when(agNamesTools.findBrandInVBank(moolah.brand)), { message: /brand(.*?)not in agoricNames.vbankAsset/, }); + const mooToken: AssetInfo = { + brand: moolah.brand, + issuer: moolah.issuer, + issuerName: 'MOO', + denom: 'umoo', + proposedName: 'MOO', + displayInfo: { decimalPlaces: 6, assetKind: 'nat' }, + }; + await E(E(agoricNamesAdmin).lookupAdmin('vbankAsset')).update( 'umoo', - /** @type {AssetInfo} */ harden({ - brand: moolah.brand, - issuer: moolah.issuer, - issuerName: 'MOO', - denom: 'umoo', - proposedName: 'MOO', - displayInfo: { decimals: 6, symbol: 'MOO' }, - }), + harden(mooToken), + ); + t.like( + await E.when(agNamesTools.findBrandInVBank(moolah.brand)), + { denom: 'umoo' }, + 'vbankAssets are refetched if brand is not found', ); + await E(E(agoricNamesAdmin).lookupAdmin('vbankAsset')).update( + 'umoo', + harden({ ...mooToken, denom: 'umoo2' }), + ); t.like( - await V.when(agNamesTools.findBrandInVBank(moolah.brand)), + await E.when(agNamesTools.findBrandInVBank(moolah.brand)), { denom: 'umoo' }, - 'refresh stale cache for new assets', + 'old AssetInfo is cached', + ); + t.like( + await E.when(agNamesTools.findBrandInVBank(moolah.brand, true)), + { denom: 'umoo2' }, + 'new AssetInfo is fetched when refetch=true', ); });