Skip to content

Commit

Permalink
refactor: exofy Orchestrator, RemoteChainFacade (#9529)
Browse files Browse the repository at this point in the history
refs: #9281

## Description
AsyncFlow requires that everything passing the membrane is durable. This makes the facade objects durable to conform.

Doing so for `localChainFacade` is deferred so we can get this into master sooner, to aid @erights 's #9521 .

### Security Considerations
none
### Scaling Considerations

Exo for each chain and each account

### Documentation Considerations
none
### Testing Considerations

Existing coverage

### Upgrade Considerations
none, not yet deployed
  • Loading branch information
mergify[bot] authored Jun 19, 2024
2 parents a035bbe + 8f1c7bd commit 37ec151
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 119 deletions.
3 changes: 2 additions & 1 deletion packages/orchestration/src/exos/local-chain-account-kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,5 @@ export const prepareLocalChainAccountKit = (
);
return makeLocalChainAccountKit;
};
/** @typedef {ReturnType<ReturnType<typeof prepareLocalChainAccountKit>>} LocalChainAccountKit */
/** @typedef {ReturnType<typeof prepareLocalChainAccountKit>} MakeLocalChainAccountKit */
/** @typedef {ReturnType<MakeLocalChainAccountKit>} LocalChainAccountKit */
101 changes: 101 additions & 0 deletions packages/orchestration/src/exos/orchestrator.js
Original file line number Diff line number Diff line change
@@ -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<LocalChain>;
* makeLocalChainAccountKit: MakeLocalChainAccountKit;
* makeRecorderKit: MakeRecorderKit;
* makeRemoteChainFacade: any;
* orchestrationService: Remote<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* timerService: Remote<TimerService>;
* 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);
91 changes: 91 additions & 0 deletions packages/orchestration/src/exos/remote-chain-facade.js
Original file line number Diff line number Diff line change
@@ -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<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* timer: Remote<TimerService>;
* }} 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<OrchestrationAccount<ChainInfo>>} */
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);
122 changes: 28 additions & 94 deletions packages/orchestration/src/facade.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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>} localchain
* @param {ReturnType<
Expand All @@ -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<ChainInfo>} */
async getChainInfo() {
return localInfo;
Expand All @@ -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);
Expand Down Expand Up @@ -89,60 +91,7 @@ const makeLocalChainFacade = (
},
};
},
};
};

/**
* @template {CosmosChainInfo} CCI
* @param {CCI} chainInfo
* @param {IBCConnectionInfo} connectionInfo
* @param {object} io
* @param {Remote<OrchestrationService>} io.orchestration
* @param {MakeRecorderKit} io.makeRecorderKit
* @param {Remote<StorageNode>} io.storageNode
* @param {Remote<TimerService>} io.timer
* @param {ZCF} io.zcf
* @param {Zone} io.zone
* @returns {Chain<CCI>}
*/
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<OrchestrationAccount<CCI>>} */
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,
});
},
};
});
};

/**
Expand All @@ -158,6 +107,8 @@ const makeRemoteChainFacade = (
* typeof import('./exos/local-chain-account-kit.js').prepareLocalChainAccountKit
* >;
* makeRecorderKit: MakeRecorderKit;
* makeCosmosOrchestrationAccount: any;
* makeRemoteChainFacade: any;
* asyncFlowTools: AsyncFlowTools;
* }} powers
*/
Expand All @@ -171,6 +122,7 @@ export const makeOrchestrationFacade = ({
chainHub,
makeLocalChainAccountKit,
makeRecorderKit,
makeRemoteChainFacade,
asyncFlowTools,
}) => {
(zone &&
Expand All @@ -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
Expand All @@ -196,40 +162,8 @@ export const makeOrchestrationFacade = ({
* @returns {(...args: Args) => Promise<unknown>}
*/
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);
},
};
Expand Down
Loading

0 comments on commit 37ec151

Please sign in to comment.