Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

9281 durable orchestrator #9532

Merged
merged 10 commits into from
Jun 19, 2024
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
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
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// FIXME handle connection closing
// FIXME handle connection closing https://github.com/Agoric/agoric-sdk/issues/9192

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What ticket capture this? Maybe vstorage logging ?

Suggested change
// FIXME storage path
// FIXME storage path https://github.com/Agoric/agoric-sdk/issues/9066

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking these up. I expect we'll get to them soon so I'll let this merge as is to save the CI time. Maybe include the links on a next PR.

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 = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice rename 👏

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a future PR we might consider updating OrchestrationAccountI['deposit'] to return Promise<Amount<'nat'>>. I don't see a reason to swallow that response.

Tangentially - with the revised implementation plan for #9193 only LocalOrchestrationAccount will have .deposit().

},
/** @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
Loading