From 66310489e0b13f8d2689536c378ab7ba6ac3f5e9 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 21 Jun 2024 00:21:21 -0400 Subject: [PATCH] feat(orchestrate): membrane friendly wrapper for agoricNames * 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 --- .../src/examples/sendAnywhere.contract.js | 14 +--- .../src/exos/agoric-names-tools.js | 66 +++++++++++++++++++ .../orchestration/src/utils/start-helper.js | 10 ++- .../test/exos/agoric-names-tools.test.ts | 25 +++++++ 4 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 packages/orchestration/src/exos/agoric-names-tools.js create mode 100644 packages/orchestration/test/exos/agoric-names-tools.test.ts diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index 5b1f415e08b1..42261ba18427 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -8,7 +8,6 @@ import { CosmosChainInfoShape } from '../typeGuards.js'; import { provideOrchestration } from '../utils/start-helper.js'; const { entries } = Object; -const { Fail } = assert; /** * @import {Baggage} from '@agoric/vat-data'; @@ -45,7 +44,7 @@ export const SingleAmountRecord = M.and( * @param {Baggage} baggage */ export const start = async (zcf, privateArgs, baggage) => { - const { chainHub, orchestrate, zone } = provideOrchestration( + const { agNamesTools, chainHub, orchestrate, zone } = provideOrchestration( zcf, baggage, privateArgs, @@ -55,15 +54,6 @@ export const start = async (zcf, privateArgs, baggage) => { /** @type {import('../orchestration-api.js').OrchestrationAccount} */ let contractAccount; - const findBrandInVBank = async brand => { - const assets = await E( - E(privateArgs.agoricNames).lookup('vbankAsset'), - ).values(); - const it = assets.find(a => a.brand === brand); - it || Fail`brand ${brand} not in agoricNames.vbankAsset`; - return it; - }; - /** @type {OfferHandler} */ const sendIt = orchestrate( 'sendIt', @@ -77,7 +67,7 @@ export const start = async (zcf, privateArgs, baggage) => { const { chainName, destAddr } = offerArgs; const { give } = seat.getProposal(); const [[kw, amt]] = entries(give); - const { denom } = await findBrandInVBank(amt.brand); + const { denom } = await V.when(agNamesTools.findBrandInVBank(amt.brand)); const chain = await orch.getChain(chainName); // FIXME ok to use a heap var crossing the membrane scope this way? diff --git a/packages/orchestration/src/exos/agoric-names-tools.js b/packages/orchestration/src/exos/agoric-names-tools.js new file mode 100644 index 000000000000..7fc1bb94a722 --- /dev/null +++ b/packages/orchestration/src/exos/agoric-names-tools.js @@ -0,0 +1,66 @@ +import { VowShape } from '@agoric/vow'; +import { E, Far } from '@endo/far'; +import { M } from '@endo/patterns'; +import { BrandShape } from '@agoric/ertp'; + +const { Fail } = assert; + +/** + * @import {NameHub} from '@agoric/vats'; + * @import {AssetInfo} from '@agoric/vats/src/vat-bank'; + * @import {Remote} from '@agoric/internal'; + * @import {Vow, VowTools} from '@agoric/vow'; + * @import {Zone} from '@agoric/zone'; + */ + +/** + * 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 + * + * XXX consider exposing `has`, `entries`, `keys`, `values` from `NameHub` + * + * @param {Zone} zone + * @param {{ agoricNames: Remote; vowTools: VowTools }} powers + */ +export const makeAgNamesTools = ( + zone, + { agoricNames, vowTools: { watch } }, +) => { + const agNamesTools = zone.exo( + 'AgoricNamesTools', + M.interface('AgoricNamesToolsI', { + lookup: M.call().rest(M.arrayOf(M.string())).returns(VowShape), + findBrandInVBank: M.call(BrandShape).returns(VowShape), + }), + { + /** @param {...string} args */ + lookup(...args) { + return watch(E(agoricNames).lookup(...args)); + }, + /** + * @param {Brand<'nat'>} brand + * @returns {Vow} + */ + findBrandInVBank(brand) { + // XXX consider caching and refetching 1x if brand is not found, + // if this is planned to be local / long-lived + const vbankAssetNameHubP = E(agoricNames).lookup('vbankAsset'); + return watch( + E(vbankAssetNameHubP).values(), + Far('BrandsWatcher', { + /** @param {AssetInfo[]} assets */ + onFulfilled(assets) { + const it = assets.find(a => a.brand === brand); + it || Fail`brand ${brand} not in agoricNames.vbankAsset`; + return it; + }, + }), + ); + }, + }, + ); + return agNamesTools; +}; +/** @typedef {ReturnType} AgNamesTools */ diff --git a/packages/orchestration/src/utils/start-helper.js b/packages/orchestration/src/utils/start-helper.js index 6b7db2acc963..e662bc00ed34 100644 --- a/packages/orchestration/src/utils/start-helper.js +++ b/packages/orchestration/src/utils/start-helper.js @@ -8,6 +8,7 @@ import { makeChainHub } from '../exos/chain-hub.js'; import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js'; import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js'; import { prepareLocalChainFacade } from '../exos/local-chain-facade.js'; +import { makeAgNamesTools } from '../exos/agoric-names-tools.js'; /** * @import {PromiseKit} from '@endo/promise-kit' @@ -45,8 +46,9 @@ export const provideOrchestration = ( marshaller, ) => { const zone = makeDurableZone(baggage); + const { agoricNames, timerService } = remotePowers; - const chainHub = makeChainHub(remotePowers.agoricNames); + const chainHub = makeChainHub(agoricNames); const vowTools = prepareVowTools(zone.subZone('vows')); @@ -55,7 +57,7 @@ export const provideOrchestration = ( zone, makeRecorderKit, zcf, - remotePowers.timerService, + timerService, vowTools, chainHub, ); @@ -64,6 +66,8 @@ export const provideOrchestration = ( vowTools, }); + const agNamesTools = makeAgNamesTools(zone, { agoricNames, vowTools }); + const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( // FIXME what zone? zone, @@ -101,6 +105,6 @@ export const provideOrchestration = ( vowTools, ...remotePowers, }); - return { ...facade, chainHub, zone }; + return { ...facade, chainHub, agNamesTools, zone }; }; harden(provideOrchestration); diff --git a/packages/orchestration/test/exos/agoric-names-tools.test.ts b/packages/orchestration/test/exos/agoric-names-tools.test.ts new file mode 100644 index 000000000000..877d5add13f6 --- /dev/null +++ b/packages/orchestration/test/exos/agoric-names-tools.test.ts @@ -0,0 +1,25 @@ +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import { V } from '@agoric/vow/vat.js'; +import { makeHeapZone } from '@agoric/zone'; +import { makeAgNamesTools } from '../../src/exos/agoric-names-tools.js'; +import { commonSetup } from '../supports.js'; + +test('agoric names tools', async t => { + const { + bootstrap: { agoricNames, vowTools }, + brands: { ist }, + } = await commonSetup(t); + + const zone = makeHeapZone(); + const agNamesTools = makeAgNamesTools(zone, { + agoricNames, + vowTools, + }); + + const chainEntry = await V.when(agNamesTools.lookup('chain', 'celestia')); + t.like(chainEntry, { chainId: 'celestia' }); + + const istDenom = await V.when(agNamesTools.findBrandInVBank(ist.brand)); + t.like(istDenom, { denom: 'uist' }); +});