From 8f1c7bdcaba96f5218ac4b4639c246773aac0aa9 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Mon, 17 Jun 2024 16:57:11 -0700 Subject: [PATCH] refactor: exofy Orchestrator, RemoteChainFacade --- .../src/exos/local-chain-account-kit.js | 3 +- .../orchestration/src/exos/orchestrator.js | 101 +++++++++++++++ .../src/exos/remote-chain-facade.js | 91 +++++++++++++ packages/orchestration/src/facade.js | 122 ++++-------------- .../orchestration/src/utils/start-helper.js | 18 +++ packages/orchestration/test/facade.test.ts | 39 +++--- 6 files changed, 255 insertions(+), 119 deletions(-) create mode 100644 packages/orchestration/src/exos/orchestrator.js create mode 100644 packages/orchestration/src/exos/remote-chain-facade.js diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index 3c5fd73f6ac..14f2935dd1c 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -278,4 +278,5 @@ export const prepareLocalChainAccountKit = ( ); return makeLocalChainAccountKit; }; -/** @typedef {ReturnType>} LocalChainAccountKit */ +/** @typedef {ReturnType} MakeLocalChainAccountKit */ +/** @typedef {ReturnType} LocalChainAccountKit */ diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js new file mode 100644 index 00000000000..294a3bcffe7 --- /dev/null +++ b/packages/orchestration/src/exos/orchestrator.js @@ -0,0 +1,101 @@ +/** @file ChainAccount exo */ +import { AmountShape } from '@agoric/ertp'; +import { makeTracer } from '@agoric/internal'; +import { V } from '@agoric/vow/vat.js'; +import { M } from '@endo/patterns'; +// eslint-disable-next-line import/no-cycle -- FIXME +import { makeLocalChainFacade } from '../facade.js'; + +/** + * @import {Zone} from '@agoric/base-zone'; + * @import {ChainHub} from '../utils/chainHub.js'; + * @import {Connection, Port} from '@agoric/network'; + * @import {AnyJson} from '@agoric/cosmic-proto'; + * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; + * @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js'; + * @import {AsyncFlowTools} from '@agoric/async-flow'; + * @import {Vow} from '@agoric/vow'; + * @import {TimerService} from '@agoric/time'; + * @import {IBCConnectionID} from '@agoric/vats'; + * @import {LocalChain} from '@agoric/vats/src/localchain.js'; + * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. + * @import {Remote} from '@agoric/internal'; + * @import {OrchestrationService} from '../service.js'; + * @import {MakeLocalChainAccountKit} from './local-chain-account-kit.js'; + * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from '../types.js'; + */ + +const { Fail } = assert; +const trace = makeTracer('Orchestrator'); + +// TODO more validation +export const ChainInfoShape = M.any(); +export const LocalChainAccountShape = M.remotable('LocalChainAccount'); +export const DenomShape = M.string(); +export const BrandInfoShape = M.any(); + +export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; + +/** @see {Orchestrator} */ +export const OrchestratorI = M.interface('Orchestrator', { + getChain: M.callWhen(M.string()).returns(ChainInfoShape), + makeLocalAccount: M.callWhen().returns(LocalChainAccountShape), + getBrandInfo: M.call(DenomShape).returns(BrandInfoShape), + asAmount: M.call(DenomAmountShape).returns(AmountShape), +}); + +/** + * @param {Zone} zone + * @param {{ + * asyncFlowTools: AsyncFlowTools; + * chainHub: ChainHub; + * localchain: Remote; + * makeLocalChainAccountKit: MakeLocalChainAccountKit; + * makeRecorderKit: MakeRecorderKit; + * makeRemoteChainFacade: any; + * orchestrationService: Remote; + * storageNode: Remote; + * timerService: Remote; + * zcf: ZCF; + * }} powers + */ +export const prepareOrchestrator = ( + zone, + { chainHub, localchain, makeLocalChainAccountKit, makeRemoteChainFacade }, +) => + zone.exoClass( + 'Orchestrator', + OrchestratorI, + () => { + trace('making an Orchestrator'); + return {}; + }, + { + /** @type {Orchestrator['getChain']} */ + getChain: async name => { + const agoricChainInfo = await chainHub.getChainInfo('agoric'); + + if (name === 'agoric') { + return makeLocalChainFacade( + localchain, + makeLocalChainAccountKit, + agoricChainInfo, + ); + } + + const remoteChainInfo = await chainHub.getChainInfo(name); + const connectionInfo = await chainHub.getConnectionInfo( + agoricChainInfo.chainId, + remoteChainInfo.chainId, + ); + + return makeRemoteChainFacade(remoteChainInfo, connectionInfo); + }, + makeLocalAccount() { + return V(localchain).makeAccount(); + }, + getBrandInfo: () => Fail`not yet implemented`, + asAmount: () => Fail`not yet implemented`, + }, + ); +harden(prepareOrchestrator); diff --git a/packages/orchestration/src/exos/remote-chain-facade.js b/packages/orchestration/src/exos/remote-chain-facade.js new file mode 100644 index 00000000000..bcf16cd573e --- /dev/null +++ b/packages/orchestration/src/exos/remote-chain-facade.js @@ -0,0 +1,91 @@ +/** @file ChainAccount exo */ +import { makeTracer } from '@agoric/internal'; +import { V } from '@agoric/vow/vat.js'; +import { M } from '@endo/patterns'; + +import { ChainInfoShape } from './orchestrator.js'; + +/** + * @import {Zone} from '@agoric/base-zone'; + * @import {TimerService} from '@agoric/time'; + * @import {Remote} from '@agoric/internal'; + * @import {OrchestrationService} from '../service.js'; + * @import {prepareCosmosOrchestrationAccount} from './cosmosOrchestrationAccount.js'; + * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount} from '../types.js'; + */ + +const { Fail } = assert; +const trace = makeTracer('RemoteChainFacade'); + +/** @type {any} */ +const anyVal = null; + +/** @see {Chain} */ +export const RemoteChainFacadeI = M.interface('RemoteChainFacade', { + getChainInfo: M.callWhen().returns(ChainInfoShape), + makeAccount: M.callWhen().returns(M.remotable('OrchestrationAccount')), +}); + +/** + * @param {Zone} zone + * @param {{ + * makeCosmosOrchestrationAccount: ReturnType< + * typeof prepareCosmosOrchestrationAccount + * >; + * orchestration: Remote; + * storageNode: Remote; + * timer: Remote; + * }} powers + */ +export const prepareRemoteChainFacade = ( + zone, + { makeCosmosOrchestrationAccount, orchestration, storageNode, timer }, +) => + zone.exoClass( + 'RemoteChainFacade', + RemoteChainFacadeI, + /** + * @param {CosmosChainInfo} remoteChainInfo + * @param {IBCConnectionInfo} connectionInfo + */ + (remoteChainInfo, connectionInfo) => { + trace('making an RemoteChainFacade'); + return { remoteChainInfo, connectionInfo }; + }, + { + async getChainInfo() { + return this.state.remoteChainInfo; + }, + + // FIXME parameterize on the remoteChainInfo to make() + // That used to work but got lost in the migration to Exo + /** @returns {Promise>} */ + async makeAccount() { + const { remoteChainInfo, connectionInfo } = this.state; + + const icaAccount = await V(orchestration).makeAccount( + remoteChainInfo.chainId, + connectionInfo.id, + connectionInfo.counterparty.connection_id, + ); + + const address = await V(icaAccount).getAddress(); + + const [{ denom: bondDenom }] = remoteChainInfo.stakingTokens || [ + { + denom: null, + }, + ]; + if (!bondDenom) { + throw Fail`missing bondDenom`; + } + return makeCosmosOrchestrationAccount(address, bondDenom, { + account: icaAccount, + storageNode, + icqConnection: anyVal, + timer, + }); + }, + }, + ); +harden(prepareRemoteChainFacade); diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 00c0670c748..8fe45036f7c 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,12 +1,15 @@ /** @file Orchestration service */ -import { V as E } from '@agoric/vow/vat.js'; import { Fail } from '@agoric/assert'; -import { prepareCosmosOrchestrationAccount } from './exos/cosmosOrchestrationAccount.js'; +import { V as E } from '@agoric/vow/vat.js'; +import { Far } from '@endo/far'; +// eslint-disable-next-line import/no-cycle -- FIXME +import { prepareOrchestrator } from './exos/orchestrator.js'; /** * @import {AsyncFlowTools} from '@agoric/async-flow'; * @import {Zone} from '@agoric/zone'; + * @import {Vow} from '@agoric/vow'; * @import {TimerService} from '@agoric/time'; * @import {IBCConnectionID} from '@agoric/vats'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; @@ -16,9 +19,7 @@ import { prepareCosmosOrchestrationAccount } from './exos/cosmosOrchestrationAcc * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from './types.js'; */ -/** @type {any} */ -const anyVal = null; - +// FIXME turn this into an Exo /** * @param {Remote} localchain * @param {ReturnType< @@ -27,12 +28,12 @@ const anyVal = null; * @param {ChainInfo} localInfo * @returns {Chain} */ -const makeLocalChainFacade = ( +export const makeLocalChainFacade = ( localchain, makeLocalChainAccountKit, localInfo, ) => { - return { + return Far('LocalChainFacade', { /** @returns {Promise} */ async getChainInfo() { return localInfo; @@ -48,6 +49,7 @@ const makeLocalChainFacade = ( storageNode: null, }); + // FIXME turn this into an Exo LocalChainOrchestrationAccount or make that a facet of makeLocalChainAccountKit return { async deposit(payment) { console.log('deposit got', payment); @@ -89,60 +91,7 @@ const makeLocalChainFacade = ( }, }; }, - }; -}; - -/** - * @template {CosmosChainInfo} CCI - * @param {CCI} chainInfo - * @param {IBCConnectionInfo} connectionInfo - * @param {object} io - * @param {Remote} io.orchestration - * @param {MakeRecorderKit} io.makeRecorderKit - * @param {Remote} io.storageNode - * @param {Remote} io.timer - * @param {ZCF} io.zcf - * @param {Zone} io.zone - * @returns {Chain} - */ -const makeRemoteChainFacade = ( - chainInfo, - connectionInfo, - { orchestration, makeRecorderKit, storageNode, timer, zcf, zone }, -) => { - const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( - zone.subZone(chainInfo.chainId), - makeRecorderKit, - zcf, - ); - - return { - getChainInfo: async () => chainInfo, - /** @returns {Promise>} */ - makeAccount: async () => { - const icaAccount = await E(orchestration).makeAccount( - chainInfo.chainId, - connectionInfo.id, - connectionInfo.counterparty.connection_id, - ); - - const address = await E(icaAccount).getAddress(); - - const [{ denom: bondDenom }] = chainInfo.stakingTokens || [ - { - denom: null, - }, - ]; - assert(bondDenom, 'missing bondDenom'); - // @ts-expect-error XXX dynamic method availability - return makeCosmosOrchestrationAccount(address, bondDenom, { - account: icaAccount, - storageNode, - icqConnection: anyVal, - timer, - }); - }, - }; + }); }; /** @@ -158,6 +107,8 @@ const makeRemoteChainFacade = ( * typeof import('./exos/local-chain-account-kit.js').prepareLocalChainAccountKit * >; * makeRecorderKit: MakeRecorderKit; + * makeCosmosOrchestrationAccount: any; + * makeRemoteChainFacade: any; * asyncFlowTools: AsyncFlowTools; * }} powers */ @@ -171,6 +122,7 @@ export const makeOrchestrationFacade = ({ chainHub, makeLocalChainAccountKit, makeRecorderKit, + makeRemoteChainFacade, asyncFlowTools, }) => { (zone && @@ -182,9 +134,23 @@ export const makeOrchestrationFacade = ({ makeLocalChainAccountKit && // @ts-expect-error type says defined but double check makeRecorderKit && + makeRemoteChainFacade && asyncFlowTools) || Fail`params missing`; + const makeOrchestrator = prepareOrchestrator(zone, { + asyncFlowTools, + chainHub, + localchain, + makeLocalChainAccountKit, + makeRecorderKit, + makeRemoteChainFacade, + orchestrationService, + storageNode, + timerService, + zcf, + }); + return { /** * @template Context @@ -196,40 +162,8 @@ export const makeOrchestrationFacade = ({ * @returns {(...args: Args) => Promise} */ orchestrate(durableName, ctx, fn) { - /** @type {Orchestrator} */ - const orc = { - async getChain(name) { - const agoricChainInfo = await chainHub.getChainInfo('agoric'); - - if (name === 'agoric') { - return makeLocalChainFacade( - localchain, - makeLocalChainAccountKit, - agoricChainInfo, - ); - } - - const remoteChainInfo = await chainHub.getChainInfo(name); - const connectionInfo = await chainHub.getConnectionInfo( - agoricChainInfo.chainId, - remoteChainInfo.chainId, - ); + const orc = makeOrchestrator(); - return makeRemoteChainFacade(remoteChainInfo, connectionInfo, { - orchestration: orchestrationService, - makeRecorderKit, - storageNode, - timer: timerService, - zcf, - zone, - }); - }, - makeLocalAccount() { - return E(localchain).makeAccount(); - }, - getBrandInfo: anyVal, - asAmount: anyVal, - }; return async (...args) => fn(orc, ctx, ...args); }, }; diff --git a/packages/orchestration/src/utils/start-helper.js b/packages/orchestration/src/utils/start-helper.js index 31e2b79b914..6fcf3fb8d36 100644 --- a/packages/orchestration/src/utils/start-helper.js +++ b/packages/orchestration/src/utils/start-helper.js @@ -5,6 +5,8 @@ import { makeDurableZone } from '@agoric/zone/durable.js'; import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js'; import { makeOrchestrationFacade } from '../facade.js'; import { makeChainHub } from './chainHub.js'; +import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js'; +import { prepareCosmosOrchestrationAccount } from '../exos/cosmosOrchestrationAccount.js'; /** * @import {PromiseKit} from '@endo/promise-kit' @@ -59,12 +61,28 @@ export const provideOrchestration = ( vowTools, }); + const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( + // XXX what zone? + zone, + makeRecorderKit, + zcf, + ); + + const makeRemoteChainFacade = prepareRemoteChainFacade(zone, { + makeCosmosOrchestrationAccount, + orchestration: remotePowers.orchestrationService, + storageNode: remotePowers.storageNode, + timer: remotePowers.timerService, + }); + const facade = makeOrchestrationFacade({ zcf, zone, chainHub, makeLocalChainAccountKit, makeRecorderKit, + makeCosmosOrchestrationAccount, + makeRemoteChainFacade, asyncFlowTools, ...remotePowers, }); diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index f042f98de39..bc5e4c915d2 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -1,14 +1,10 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { prepareAsyncFlowTools } from '@agoric/async-flow'; import { setupZCFTest } from '@agoric/zoe/test/unitTests/zcf/setupZcfTest.js'; -import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; -import { prepareVowTools } from '@agoric/vow'; import type { CosmosChainInfo, IBCConnectionInfo } from '../src/cosmos-api.js'; -import { makeOrchestrationFacade } from '../src/facade.js'; import type { Chain } from '../src/orchestration-api.js'; +import { provideOrchestration } from '../src/utils/start-helper.js'; import { commonSetup } from './supports.js'; -import { makeChainHub } from '../src/utils/chainHub.js'; const test = anyTest; @@ -45,30 +41,25 @@ export const mockChainConnection: IBCConnectionInfo = { const makeLocalChainAccountKit = () => assert.fail(`not used`); test('chain info', async t => { - const { bootstrap, facadeServices } = await commonSetup(t); + const { bootstrap, facadeServices, commonPrivateArgs } = await commonSetup(t); const zone = bootstrap.rootZone; const { zcf } = await setupZCFTest(); - const chainHub = makeChainHub(facadeServices.agoricNames); - const { makeRecorderKit } = prepareRecorderKitMakers( - zone.mapStore('recorder'), - bootstrap.marshaller, - ); - const vowTools = prepareVowTools(zone.subZone('vows')); - const asyncFlowTools = prepareAsyncFlowTools(zone.subZone('asyncFlow'), { - vowTools, - }); - const { orchestrate } = makeOrchestrationFacade({ - ...facadeServices, - storageNode: bootstrap.storage.rootNode, + const orchKit = provideOrchestration( zcf, - zone, - chainHub, - makeLocalChainAccountKit, - makeRecorderKit, - asyncFlowTools, - }); + zone.mapStore('test'), + { + agoricNames: facadeServices.agoricNames, + timerService: facadeServices.timerService, + storageNode: commonPrivateArgs.storageNode, + orchestrationService: facadeServices.orchestrationService, + localchain: facadeServices.localchain, + }, + commonPrivateArgs.marshaller, + ); + + const { chainHub, orchestrate } = orchKit; chainHub.registerChain('mock', mockChainInfo); chainHub.registerConnection(