Skip to content

Commit

Permalink
9281 durable orchestrator (#9532)
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.

Following #9529 

### Security Considerations
none

### Scaling Considerations
Exo for each chain and each account


### Documentation Considerations
none

### Testing Considerations
Existing coverage


### Upgrade Considerations
not yet deployed
  • Loading branch information
mergify[bot] authored Jun 19, 2024
2 parents 37ec151 + 62bdb8a commit c244e96
Show file tree
Hide file tree
Showing 25 changed files with 250 additions and 214 deletions.
2 changes: 1 addition & 1 deletion packages/orchestration/src/chain-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import fetchedChainInfo from './fetched-chain-info.js';
const knownChains = /** @satisfies {Record<string, ChainInfo>} */ (
harden({
...fetchedChainInfo,
// XXX does not have useful connections
// FIXME does not have useful connections
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9492
agoriclocal: {
chainId: 'agoriclocal',
Expand Down
5 changes: 3 additions & 2 deletions packages/orchestration/src/examples/sendAnywhere.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const start = async (zcf, privateArgs, baggage) => {
privateArgs.marshaller,
);

/** @type {import('../orchestration-api.js').OrchestrationAccount<any>} */
let contractAccount;

const findBrandInVBank = async brand => {
Expand Down Expand Up @@ -79,7 +80,7 @@ export const start = async (zcf, privateArgs, baggage) => {
const { denom } = await findBrandInVBank(amt.brand);
const chain = await orch.getChain(chainName);

// XXX ok to use a heap var crossing the membrane scope this way?
// FIXME ok to use a heap var crossing the membrane scope this way?
if (!contractAccount) {
const agoricChain = await orch.getChain('agoric');
contractAccount = await agoricChain.makeAccount();
Expand All @@ -88,7 +89,7 @@ export const start = async (zcf, privateArgs, baggage) => {
const info = await chain.getChainInfo();
const { chainId } = info;
const { [kw]: pmtP } = await withdrawFromSeat(zcf, seat, give);
await E.when(pmtP, pmt => contractAccount.deposit(pmt, amt));
await E.when(pmtP, pmt => contractAccount.deposit(pmt));
await contractAccount.transfer(
{ denom, value: amt.value },
{
Expand Down
14 changes: 9 additions & 5 deletions packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { V } from '@agoric/vow/vat.js';
import { E } from '@endo/far';
import { deeplyFulfilled } from '@endo/marshal';
import { M } from '@endo/patterns';
import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js';
import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js';
import { makeChainHub } from '../utils/chainHub.js';

/**
Expand Down Expand Up @@ -41,7 +41,7 @@ export const start = async (zcf, privateArgs, baggage) => {
privateArgs.marshaller,
);

const makeLocalChainAccountKit = prepareLocalChainAccountKit(
const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit(
zone,
makeRecorderKit,
zcf,
Expand All @@ -58,10 +58,14 @@ export const start = async (zcf, privateArgs, baggage) => {
async function makeLocalAccountKit() {
const account = await V(privateArgs.localchain).makeAccount();
const address = await V(account).getAddress();
// XXX 'address' is implied by 'account'; use an async maker that get the value itself
return makeLocalChainAccountKit({
// FIXME 'address' is implied by 'account'; use an async maker that get the value itself
return makeLocalOrchestrationAccountKit({
account,
address,
address: harden({
address,
addressEncoding: 'bech32',
chainId: 'local',
}),
storageNode: privateArgs.storageNode,
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/examples/stakeIca.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { M } from '@endo/patterns';
import { prepareCosmosOrchestrationAccount } from '../exos/cosmosOrchestrationAccount.js';
import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js';

const trace = makeTracer('StakeAtom');
/**
Expand Down
7 changes: 6 additions & 1 deletion packages/orchestration/src/examples/swapExample.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ export const start = async (zcf, privateArgs, baggage) => {

// deposit funds from user seat to LocalChainAccount
const payments = await withdrawFromSeat(zcf, seat, give);
await deeplyFulfilled(objectMap(payments, localAccount.deposit));
await deeplyFulfilled(
objectMap(payments, payment =>
// @ts-expect-error payment is ERef<Payment> which happens to work but isn't officially supported
localAccount.deposit(payment),
),
);
seat.exit();

// build swap instructions with orcUtils library
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const prepareChainAccountKit = zone =>
},
/** Close the remote account */
async close() {
/// XXX what should the behavior be here? and `onClose`?
// FIXME what should the behavior be here? and `onClose`?
// - retrieve assets?
// - revoke the port?
const { connection } = this.state;
Expand Down Expand Up @@ -169,7 +169,7 @@ export const prepareChainAccountKit = zone =>
},
async onClose(_connection, reason) {
trace(`ICA Channel closed. Reason: ${reason}`);
// XXX handle connection closing
// FIXME handle connection closing
// XXX is there a scenario where a connection will unexpectedly close? _I think yes_
},
async onReceive(connection, bytes) {
Expand Down
67 changes: 67 additions & 0 deletions packages/orchestration/src/exos/local-chain-facade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** @file ChainAccount exo */
import { V } from '@agoric/vow/vat.js';

import { ChainFacadeI } from '../typeGuards.js';

/**
* @import {Zone} from '@agoric/base-zone';
* @import {TimerService} from '@agoric/time';
* @import {Remote} from '@agoric/internal';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {OrchestrationService} from '../service.js';
* @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js';
* @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount} from '../types.js';
*/

/**
* @param {Zone} zone
* @param {{
* makeLocalOrchestrationAccountKit: MakeLocalOrchestrationAccountKit;
* orchestration: Remote<OrchestrationService>;
* storageNode: Remote<StorageNode>;
* timer: Remote<TimerService>;
* localchain: Remote<LocalChain>;
* }} powers
*/
export const prepareLocalChainFacade = (
zone,
{ makeLocalOrchestrationAccountKit, localchain, storageNode },
) =>
zone.exoClass(
'LocalChainFacade',
ChainFacadeI,
/**
* @param {CosmosChainInfo} localChainInfo
*/
localChainInfo => {
return { localChainInfo };
},
{
async getChainInfo() {
return this.state.localChainInfo;
},

// 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 { localChainInfo } = this.state;
const lcaP = V(localchain).makeAccount();
const [lca, address] = await Promise.all([lcaP, V(lcaP).getAddress()]);
const { holder: account } = makeLocalOrchestrationAccountKit({
account: lca,
address: harden({
address,
chainId: localChainInfo.chainId,
addressEncoding: 'bech32',
}),
// FIXME storage path
storageNode,
});

return account;
},
},
);
harden(prepareLocalChainFacade);
/** @typedef {ReturnType<typeof prepareLocalChainFacade>} MakeLocalChainFacade */
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
/** @file Use-object for the owner of a localchain account */
import { NonNullish } from '@agoric/assert';
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 { V } from '@agoric/vow/vat.js';
import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { V } from '@agoric/vow/vat.js';
import { E } from '@endo/far';
import {
AmountArgShape,
ChainAddressShape,
IBCTransferOptionsShape,
} from '../typeGuards.js';
import { maxClockSkew } from '../utils/cosmos.js';
import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js';
import { dateInSeconds, makeTimestampHelper } from '../utils/time.js';

/**
* @import {LocalChainAccount} from '@agoric/vats/src/localchain.js';
* @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, CosmosChainInfo} from '@agoric/orchestration';
* @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, OrchestrationAccountI} from '@agoric/orchestration';
* @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'.
* @import {Zone} from '@agoric/zone';
* @import {Remote} from '@agoric/internal';
* @import {TimerService, TimerBrand} from '@agoric/time';
* @import {ChainHub} from '../utils/chainHub.js';
*/

const trace = makeTracer('LCAH');
const trace = makeTracer('LOA');

const { Fail } = assert;
/**
Expand All @@ -38,20 +33,16 @@ const { Fail } = assert;
* @typedef {{
* topicKit: RecorderKit<LocalChainAccountNotification>;
* account: LocalChainAccount;
* address: ChainAddress['address'];
* address: ChainAddress;
* }} State
*/

const HolderI = M.interface('holder', {
...orchestrationAccountMethods,
getPublicTopics: M.call().returns(TopicsRecordShape),
delegate: M.call(M.string(), AmountShape).returns(M.promise()),
undelegate: M.call(M.string(), AmountShape).returns(M.promise()),
deposit: M.callWhen(PaymentShape).optional(AmountShape).returns(AmountShape),
withdraw: M.callWhen(AmountShape).returns(PaymentShape),
transfer: M.call(AmountArgShape, ChainAddressShape)
.optional(IBCTransferOptionsShape)
.returns(M.promise()),
getAddress: M.call().returns(M.string()),
executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())),
});

Expand All @@ -67,18 +58,18 @@ const PUBLIC_TOPICS = {
* @param {Remote<TimerService>} timerService
* @param {ChainHub} chainHub
*/
export const prepareLocalChainAccountKit = (
export const prepareLocalOrchestrationAccountKit = (
zone,
makeRecorderKit,
zcf,
timerService,
chainHub,
) => {
const timestampHelper = makeTimestampHelper(timerService);
// TODO: rename to makeLocalOrchestrationAccount or the like to distinguish from lca

/** Make an object wrapping an LCA with Zoe interfaces. */
const makeLocalChainAccountKit = zone.exoClassKit(
'LCA Kit',
const makeLocalOrchestrationAccountKit = zone.exoClassKit(
'Local Orchestration Account Kit',
{
holder: HolderI,
invitationMakers: M.interface('invitationMakers', {
Expand All @@ -92,16 +83,15 @@ export const prepareLocalChainAccountKit = (
/**
* @param {object} initState
* @param {LocalChainAccount} initState.account
* @param {ChainAddress['address']} initState.address
* @param {StorageNode} initState.storageNode
* @param {ChainAddress} initState.address
* @param {Remote<StorageNode>} initState.storageNode
* @returns {State}
*/
({ account, address, storageNode }) => {
// must be the fully synchronous maker because the kit is held in durable state
// @ts-expect-error XXX Patterns
const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]);

// #9162 use ChainAddress object instead of `address` string
return { account, address, topicKit };
},
{
Expand Down Expand Up @@ -138,6 +128,24 @@ export const prepareLocalChainAccountKit = (
},
},
holder: {
/** @type {OrchestrationAccount<any>['getBalance']} */
async getBalance(denomArg) {
// FIXME look up real values
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9211
const [brand, denom] =
typeof denomArg === 'string'
? [/** @type {any} */ (null), denomArg]
: [denomArg, 'FIXME'];

const natAmount = await V.when(
E(this.state.account).getBalance(brand),
);
return harden({ denom, value: natAmount.value });
},
getBalances() {
throw new Error('not yet implemented');
},

getPublicTopics() {
const { topicKit } = this.state;
return harden({
Expand Down Expand Up @@ -207,9 +215,9 @@ export const prepareLocalChainAccountKit = (
* updater will get a special notification that the account is being
* transferred.
*/
/** @type {LocalChainAccount['deposit']} */
async deposit(payment, optAmountShape) {
return V(this.state.account).deposit(payment, optAmountShape);
/** @type {OrchestrationAccount<any>['deposit']} */
async deposit(payment) {
await V(this.state.account).deposit(payment);
},
/** @type {LocalChainAccount['withdraw']} */
async withdraw(amount) {
Expand All @@ -220,9 +228,13 @@ export const prepareLocalChainAccountKit = (
// @ts-expect-error subtype
return V(this.state.account).executeTx(messages);
},
/** @returns {ChainAddress['address']} */
/** @returns {ChainAddress} */
getAddress() {
return NonNullish(this.state.address, 'Chain address not available.');
return this.state.address;
},
async send(toAccount, amount) {
// FIXME implement
console.log('send got', toAccount, amount);
},
/**
* @param {AmountArg} amount an ERTP {@link Amount} or a
Expand Down Expand Up @@ -261,7 +273,7 @@ export const prepareLocalChainAccountKit = (
amount: String(amount.value),
denom: amount.denom,
},
sender: this.state.address,
sender: this.state.address.address,
receiver: destination.address,
timeoutHeight: opts?.timeoutHeight ?? {
revisionHeight: 0n,
Expand All @@ -273,10 +285,15 @@ export const prepareLocalChainAccountKit = (
]);
trace('MsgTransfer result', result);
},
/** @type {OrchestrationAccount<any>['transferSteps']} */
transferSteps(amount, msg) {
console.log('transferSteps got', amount, msg);
return Promise.resolve();
},
},
},
);
return makeLocalChainAccountKit;
return makeLocalOrchestrationAccountKit;
};
/** @typedef {ReturnType<typeof prepareLocalChainAccountKit>} MakeLocalChainAccountKit */
/** @typedef {ReturnType<MakeLocalChainAccountKit>} LocalChainAccountKit */
/** @typedef {ReturnType<typeof prepareLocalOrchestrationAccountKit>} MakeLocalOrchestrationAccountKit */
/** @typedef {ReturnType<MakeLocalOrchestrationAccountKit>} LocalOrchestrationAccountKit */
Loading

0 comments on commit c244e96

Please sign in to comment.