diff --git a/packages/orchestration/src/chain-info.js b/packages/orchestration/src/chain-info.js index 3cbc182bb5c..b76fea8f8c4 100644 --- a/packages/orchestration/src/chain-info.js +++ b/packages/orchestration/src/chain-info.js @@ -1,7 +1,8 @@ -import { registerChain } from './exos/chain-hub.js'; - -// Refresh with scripts/refresh-chain-info.ts -import fetchedChainInfo from './fetched-chain-info.js'; +import { E } from '@endo/far'; +import { mustMatch } from '@endo/patterns'; +import { connectionKey } from './exos/chain-hub.js'; +import fetchedChainInfo from './fetched-chain-info.js'; // Refresh with scripts/refresh-chain-info.ts +import { CosmosChainInfoShape } from './typeGuards.js'; /** @import {CosmosChainInfo, EthChainInfo} from './types.js'; */ @@ -64,6 +65,44 @@ const knownChains = /** @satisfies {Record} */ ( /** @typedef {typeof knownChains} KnownChains */ +/** + * @param {ERef} agoricNamesAdmin + * @param {string} name + * @param {CosmosChainInfo} chainInfo + * @param {(...messages: string[]) => void} log + */ +export const registerChain = async ( + agoricNamesAdmin, + name, + chainInfo, + log = () => {}, +) => { + const { nameAdmin } = await E(agoricNamesAdmin).provideChild('chain'); + const { nameAdmin: connAdmin } = + await E(agoricNamesAdmin).provideChild('chainConnection'); + + mustMatch(chainInfo, CosmosChainInfoShape); + const { connections = {}, ...vertex } = chainInfo; + + const promises = [ + E(nameAdmin) + .update(name, vertex) + .then(() => log(`registered agoricNames chain.${name}`)), + ]; + + // FIXME updates redundantly, twice per edge + for (const [counterChainId, connInfo] of Object.entries(connections)) { + const key = connectionKey(chainInfo.chainId, counterChainId); + promises.push( + E(connAdmin) + .update(key, connInfo) + .then(() => log(`registering agoricNames chainConnection.${key}`)), + ); + } + // Bundle to pipeline IO + await Promise.all(promises); +}; + /** * @param {ERef} agoricNamesAdmin * @param {(...messages: string[]) => void} log diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index 5319e1fe0f9..5b1f415e08b 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -2,7 +2,7 @@ import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js' import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { E } from '@endo/far'; import { M, mustMatch } from '@endo/patterns'; - +import { V } from '@agoric/vow/vat.js'; import { AmountShape } from '@agoric/ertp'; import { CosmosChainInfoShape } from '../typeGuards.js'; import { provideOrchestration } from '../utils/start-helper.js'; @@ -134,7 +134,9 @@ export const start = async (zcf, privateArgs, baggage) => { */ async addChain(chainInfo, connectionInfo) { const chainKey = `${chainInfo.chainId}-${(nonce += 1n)}`; - const agoricChainInfo = await chainHub.getChainInfo('agoric'); + // when() because chainHub methods return vows. If this were inside + // orchestrate() the membrane would wrap/unwrap automatically. + const agoricChainInfo = await V.when(chainHub.getChainInfo('agoric')); chainHub.registerChain(chainKey, chainInfo); chainHub.registerConnection( agoricChainInfo.chainId, diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index 321242ff812..6d505f8d6cb 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -1,12 +1,15 @@ -import { E } from '@endo/far'; -import { M, mustMatch } from '@endo/patterns'; +import { VowShape } from '@agoric/vow'; +import { allVows, watch } from '@agoric/vow/vat.js'; import { makeHeapZone } from '@agoric/zone'; +import { E } from '@endo/far'; +import { M } from '@endo/patterns'; import { CosmosChainInfoShape, IBCConnectionInfoShape } from '../typeGuards.js'; const { Fail } = assert; /** * @import {NameHub} from '@agoric/vats'; + * @import {Vow} from '@agoric/vow'; * @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api.js'; * @import {ChainInfo, KnownChains} from '../chain-info.js'; * @import {Remote} from '@agoric/internal'; @@ -67,15 +70,14 @@ const ChainIdArgShape = M.or( const ChainHubI = M.interface('ChainHub', { registerChain: M.call(M.string(), CosmosChainInfoShape).returns(), - getChainInfo: M.callWhen(M.string()).returns(CosmosChainInfoShape), - registerConnection: M.callWhen( + getChainInfo: M.call(M.string()).returns(VowShape), + registerConnection: M.call( M.string(), M.string(), IBCConnectionInfoShape, ).returns(), - getConnectionInfo: M.callWhen(ChainIdArgShape, ChainIdArgShape).returns( - IBCConnectionInfoShape, - ), + getConnectionInfo: M.call(ChainIdArgShape, ChainIdArgShape).returns(VowShape), + getChainsAndConnection: M.call(M.string(), M.string()).returns(VowShape), }); /** @@ -120,22 +122,25 @@ export const makeChainHub = (agoricNames, zone = makeHeapZone()) => { /** * @template {string} K * @param {K} chainName - * @returns {Promise>} + * @returns {Vow>} */ - async getChainInfo(chainName) { + getChainInfo(chainName) { // Either from registerChain or memoized remote lookup() if (chainInfos.has(chainName)) { - // @ts-expect-error cast - return chainInfos.get(chainName); + return /** @type {Vow>} */ ( + watch(chainInfos.get(chainName)) + ); } - const chainInfo = await E(agoricNames) - .lookup(CHAIN_KEY, chainName) - .catch(_cause => { + return watch(E(agoricNames).lookup(CHAIN_KEY, chainName), { + onFulfilled: chainInfo => { + chainInfos.init(chainName, chainInfo); + return chainInfo; + }, + onRejected: _cause => { throw assert.error(`chain not found:${chainName}`); - }); - chainInfos.init(chainName, chainInfo); - return chainInfo; + }, + }); }, /** * @param {string} chainId1 @@ -150,88 +155,55 @@ export const makeChainHub = (agoricNames, zone = makeHeapZone()) => { /** * @param {string | { chainId: string }} chain1 * @param {string | { chainId: string }} chain2 - * @returns {Promise} + * @returns {Vow} */ - async getConnectionInfo(chain1, chain2) { + getConnectionInfo(chain1, chain2) { const chainId1 = typeof chain1 === 'string' ? chain1 : chain1.chainId; const chainId2 = typeof chain2 === 'string' ? chain2 : chain2.chainId; const key = connectionKey(chainId1, chainId2); if (connectionInfos.has(key)) { - return connectionInfos.get(key); + return watch(connectionInfos.get(key)); } - const connectionInfo = await E(agoricNames) - .lookup(CONNECTIONS_KEY, key) - .catch(_cause => { + return watch(E(agoricNames).lookup(CONNECTIONS_KEY, key), { + onFulfilled: connectionInfo => { + connectionInfos.init(key, connectionInfo); + return connectionInfo; + }, + onRejected: _cause => { throw assert.error(`connection not found: ${chainId1}<->${chainId2}`); - }); - connectionInfos.init(key, connectionInfo); - return connectionInfo; + }, + }); + }, + + /** + * @template {string} C1 + * @template {string} C2 + * @param {C1} chainName1 + * @param {C2} chainName2 + * @returns {Vow< + * [ActualChainInfo, ActualChainInfo, IBCConnectionInfo] + * >} + */ + getChainsAndConnection(chainName1, chainName2) { + return watch( + allVows([ + chainHub.getChainInfo(chainName1), + chainHub.getChainInfo(chainName2), + ]), + { + onFulfilled: ([chain1, chain2]) => { + return watch(chainHub.getConnectionInfo(chain2, chain1), { + onFulfilled: connectionInfo => { + return [chain1, chain2, connectionInfo]; + }, + }); + }, + }, + ); }, }); return chainHub; }; /** @typedef {ReturnType} ChainHub */ - -/** - * @param {ERef} agoricNamesAdmin - * @param {string} name - * @param {CosmosChainInfo} chainInfo - * @param {(...messages: string[]) => void} log - */ -export const registerChain = async ( - agoricNamesAdmin, - name, - chainInfo, - log = () => {}, -) => { - const { nameAdmin } = await E(agoricNamesAdmin).provideChild('chain'); - const { nameAdmin: connAdmin } = - await E(agoricNamesAdmin).provideChild('chainConnection'); - - mustMatch(chainInfo, CosmosChainInfoShape); - const { connections = {}, ...vertex } = chainInfo; - - const promises = [ - E(nameAdmin) - .update(name, vertex) - .then(() => log(`registered agoricNames chain.${name}`)), - ]; - - // FIXME updates redundantly, twice per edge - for await (const [counterChainId, connInfo] of Object.entries(connections)) { - const key = connectionKey(chainInfo.chainId, counterChainId); - promises.push( - E(connAdmin) - .update(key, connInfo) - .then(() => log(`registering agoricNames chainConnection.${key}`)), - ); - } - // Bundle to pipeline IO - await Promise.all(promises); -}; - -/** - * @template {string} C1 - * @template {string} C2 - * @param {ChainHub} chainHub - * @param {C1} chainName1 - * @param {C2} chainName2 - * @returns {Promise< - * [ActualChainInfo, ActualChainInfo, IBCConnectionInfo] - * >} - */ -export const getChainsAndConnection = async ( - chainHub, - chainName1, - chainName2, -) => { - const [chain1, chain2] = await Promise.all([ - chainHub.getChainInfo(chainName1), - chainHub.getChainInfo(chainName2), - ]); - const connectionInfo = await chainHub.getConnectionInfo(chain2, chain1); - - return [chain1, chain2, connectionInfo]; -}; diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 02a59a86b24..bce4acc5235 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -3,6 +3,7 @@ import { typedJson } from '@agoric/cosmic-proto/vatsafe'; import { AmountShape, PaymentShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { M } from '@agoric/vat-data'; +import { VowShape } from '@agoric/vow'; import { V } from '@agoric/vow/vat.js'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; @@ -89,7 +90,7 @@ export const prepareLocalOrchestrationAccountKit = ( getChainInfoWatcher: M.interface('getChainInfoWatcher', { onFulfilled: M.call(M.record()) // agoric chain info .optional({ destination: ChainAddressShape }) // empty context - .returns(M.promise()), // transfer channel + .returns(VowShape), // transfer channel }), getTimeoutTimestampWatcher: M.interface('getTimeoutTimestampWatcher', { onFulfilled: M.call(M.bigint()) diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index d6982f8bd39..5c5923af6b1 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -4,13 +4,12 @@ import { makeTracer } from '@agoric/internal'; import { E } from '@endo/far'; import { M } from '@endo/patterns'; import { - ChainInfoShape, - LocalChainAccountShape, - DenomShape, BrandInfoShape, + ChainInfoShape, DenomAmountShape, + DenomShape, + LocalChainAccountShape, } from '../typeGuards.js'; -import { getChainsAndConnection } from './chain-hub.js'; /** * @import {Zone} from '@agoric/base-zone'; @@ -78,7 +77,7 @@ export const prepareOrchestratorKit = ( makeRemoteChainFacadeWatcher: M.interface( 'makeRemoteChainFacadeWatcher', { - onFulfilled: M.call(M.arrayOf(M.record())) + onFulfilled: M.call(M.any()) .optional(M.arrayOf(M.undefined())) .returns(M.any()), // FIXME narrow }, @@ -115,6 +114,7 @@ export const prepareOrchestratorKit = ( /** @type {Orchestrator['getChain']} */ getChain(name) { if (name === 'agoric') { + // XXX when() until membrane return when( watch( chainHub.getChainInfo('agoric'), @@ -122,9 +122,10 @@ export const prepareOrchestratorKit = ( ), ); } + // XXX when() until membrane return when( watch( - getChainsAndConnection(chainHub, 'agoric', name), + chainHub.getChainsAndConnection('agoric', name), this.facets.makeRemoteChainFacadeWatcher, ), ); diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 35de5e2b4bb..122df9dbc65 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -84,18 +84,23 @@ export const makeOrchestrationFacade = ({ return { /** + * @template Return * @template Context * @template {any[]} Args * @param {string} durableName - the orchestration flow identity in the zone * (to resume across upgrades) * @param {Context} ctx - values to pass through the async flow membrane - * @param {(orc: Orchestrator, ctx2: Context, ...args: Args) => object} fn - * @returns {(...args: Args) => Promise} + * @param {( + * orc: Orchestrator, + * ctx2: Context, + * ...args: Args + * ) => Promise} fn + * @returns {(...args: Args) => Promise} */ orchestrate(durableName, ctx, fn) { const orc = makeOrchestrator(); - return async (...args) => fn(orc, ctx, ...args); + return async (...args) => vowTools.when(fn(orc, ctx, ...args)); }, }; }; diff --git a/packages/orchestration/src/proposals/start-stakeAtom.js b/packages/orchestration/src/proposals/start-stakeAtom.js index 05a6069dd9f..d94be617a4a 100644 --- a/packages/orchestration/src/proposals/start-stakeAtom.js +++ b/packages/orchestration/src/proposals/start-stakeAtom.js @@ -1,7 +1,8 @@ import { makeTracer } from '@agoric/internal'; import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; import { E } from '@endo/far'; -import { getChainsAndConnection, makeChainHub } from '../exos/chain-hub.js'; +import { V } from '@agoric/vow/vat.js'; +import { makeChainHub } from '../exos/chain-hub.js'; /** * @import {IBCConnectionID} from '@agoric/vats'; @@ -46,10 +47,8 @@ export const startStakeAtom = async ({ const chainHub = makeChainHub(await agoricNames); - const [_, cosmoshub, connectionInfo] = await getChainsAndConnection( - chainHub, - 'agoric', - 'cosmoshub', + const [_, cosmoshub, connectionInfo] = await V.when( + chainHub.getChainsAndConnection('agoric', 'cosmoshub'), ); /** @type {StartUpgradableOpts} */ diff --git a/packages/orchestration/src/proposals/start-stakeOsmo.js b/packages/orchestration/src/proposals/start-stakeOsmo.js index c7911c5e74c..e8bb0dc39ad 100644 --- a/packages/orchestration/src/proposals/start-stakeOsmo.js +++ b/packages/orchestration/src/proposals/start-stakeOsmo.js @@ -1,7 +1,8 @@ import { makeTracer } from '@agoric/internal'; import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; +import { V } from '@agoric/vow/vat.js'; import { E } from '@endo/far'; -import { getChainsAndConnection, makeChainHub } from '../exos/chain-hub.js'; +import { makeChainHub } from '../exos/chain-hub.js'; /** * @import {IBCConnectionID} from '@agoric/vats'; @@ -47,10 +48,8 @@ export const startStakeOsmo = async ({ const chainHub = makeChainHub(await agoricNames); - const [_, osmosis, connectionInfo] = await getChainsAndConnection( - chainHub, - 'agoric', - 'osmosis', + const [_, osmosis, connectionInfo] = await V.when( + chainHub.getChainsAndConnection('agoric', 'osmosis'), ); /** @type {StartUpgradableOpts} */ diff --git a/packages/orchestration/test/examples/sendAnywhere.test.ts b/packages/orchestration/test/examples/sendAnywhere.test.ts index d3d44833155..35e0f049404 100644 --- a/packages/orchestration/test/examples/sendAnywhere.test.ts +++ b/packages/orchestration/test/examples/sendAnywhere.test.ts @@ -3,13 +3,12 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E } from '@endo/far'; import path from 'path'; - import { mustMatch } from '@endo/patterns'; import { makeIssuerKit } from '@agoric/ertp'; import { CosmosChainInfo, IBCConnectionInfo } from '../../src/cosmos-api.js'; import { commonSetup } from '../supports.js'; import { SingleAmountRecord } from '../../src/examples/sendAnywhere.contract.js'; -import { registerChain } from '../../src/exos/chain-hub.js'; +import { registerChain } from '../../src/chain-info.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index 516d07a44c6..f6a235de0a1 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -2,6 +2,7 @@ import test from '@endo/ses-ava/prepare-endo.js'; import { makeNameHubKit } from '@agoric/vats'; +import { V } from '@agoric/vow/vat.js'; import { makeChainHub } from '../../src/exos/chain-hub.js'; const connection = { @@ -37,8 +38,11 @@ test('getConnectionInfo', async t => { // Look up by string or info object t.deepEqual( - await chainHub.getConnectionInfo(aChain.chainId, bChain.chainId), + await V.when(chainHub.getConnectionInfo(aChain.chainId, bChain.chainId)), + connection, + ); + t.deepEqual( + await V.when(chainHub.getConnectionInfo(aChain, bChain)), connection, ); - t.deepEqual(await chainHub.getConnectionInfo(aChain, bChain), connection); });