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

9693 lint flow #9726

Merged
merged 8 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/async-flow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@
"workerThreads": false
},
"typeCoverage": {
"atLeast": 76.94
"atLeast": 77.01
}
}
4 changes: 3 additions & 1 deletion packages/async-flow/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export type GuestAsyncFunc = (
...activationArgs: Guest[]
) => Guest<Promise<any>>;

export type HostAsyncFuncWrapper = (...activationArgs: Host[]) => HostVow;
export type HostAsyncFuncWrapper = (
...activationArgs: Host<any>[]
) => HostVow<any>;

/**
* The function from the host as it will be available in the guest.
Expand Down
19 changes: 19 additions & 0 deletions packages/eslint-config/eslint-config.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
/* eslint-env node */

const orchestrationFlowRestrictions = [
{
selector: "Identifier[name='heapVowE']",
message: 'Eventual send is not yet supported within an orchestration flow',
},
{
selector: "Identifier[name='E']",
message: 'Eventual send is not yet supported within an orchestration flow',
},
];

module.exports = {
extends: [
'airbnb-base',
Expand Down Expand Up @@ -95,5 +107,12 @@ module.exports = {
'no-unused-vars': 'off',
},
},
{
// Orchestration flows
files: ['**/*.flows.js'],
rules: {
'no-restricted-syntax': ['error', ...orchestrationFlowRestrictions],
},
},
],
};
15 changes: 15 additions & 0 deletions packages/orchestration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,18 @@
Build feature-rich applications that can orchestrate assets and services across the interchain.

Usage examples can be found under [src/examples](https://github.com/Agoric/agoric-sdk/tree/master/packages/orchestration/src/examples). They are exported for integration testing with other packages.

## Orchestration flows
Copy link
Member

Choose a reason for hiding this comment

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

only skimmed this


Flows to orchestrate are regular Javascript functions but have some constraints to fulfill the requirements of resumability after termination of the enclosing vat. Some requirements for each orchestration flow:
- must not close over any values that could change between invocations
- must satisfy the `OrchestrationFlow` interface
- must be hardened
- must not use `E()` (eventual send)

The call to `orchestrate` using a flow function in reincarnations of the vat must have the same `durableName` as before. To help enforce these constraints, we recommend:

- keeping flows in a `.flows.js` module
- importing them all with `import * as flows` to get a single object keyed by the export name
- using `orchestrateAll` to treat each export name as the `durableName` of the flow
- adopting `@agoric/eslint-config` that has rules to help detect problems
2 changes: 1 addition & 1 deletion packages/orchestration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,6 @@
"access": "public"
},
"typeCoverage": {
"atLeast": 98.09
"atLeast": 98.05
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js';
* @import {Remote} from '@agoric/vow';
* @import {Zone} from '@agoric/zone';
* @import {GuestInterface} from '@agoric/async-flow';
* @import {CosmosValidatorAddress, Orchestrator, CosmosInterchainService, Denom, OrchestrationAccount, StakingAccountActions} from '@agoric/orchestration';
* @import {CosmosValidatorAddress, Orchestrator, CosmosInterchainService, Denom, OrchestrationAccount, StakingAccountActions, OrchestrationFlow} from '@agoric/orchestration';
* @import {MakeStakingTap} from './auto-stake-it-tap-kit.js';
* @import {MakePortfolioHolder} from '../exos/portfolio-holder-kit.js';
* @import {ChainHub} from '../exos/chain-hub.js';
Expand All @@ -35,6 +35,7 @@ import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js';
*/

/**
* @satisfies {OrchestrationFlow}
Copy link
Member

Choose a reason for hiding this comment

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

👍

* @param {Orchestrator} orch
* @param {{
* makeStakingTap: MakeStakingTap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js';

/**
* @import {Zone} from '@agoric/zone';
* @import {OrchestrationAccount, Orchestrator} from '@agoric/orchestration';
* @import {OrchestrationAccount, OrchestrationFlow, Orchestrator} from '@agoric/orchestration';
* @import {ResolvedPublicTopic} from '@agoric/zoe/src/contractSupport/topics.js';
* @import {OrchestrationPowers} from '../utils/start-helper.js';
* @import {MakePortfolioHolder} from '../exos/portfolio-holder-kit.js';
Expand All @@ -20,6 +20,7 @@ import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js';
* Create an account on a Cosmos chain and return a continuing offer with
* invitations makers for Delegate, WithdrawRewards, Transfer, etc.
*
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {undefined} _ctx
* @param {ZCFSeat} seat
Expand All @@ -39,6 +40,7 @@ const makeOrchAccountHandler = async (orch, _ctx, seat, { chainName }) => {
* Calls to the underlying invitationMakers are proxied through the
* `MakeInvitation` invitation maker.
*
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {MakePortfolioHolder} makePortfolioHolder
* @param {ZCFSeat} seat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Fail } from '@endo/errors';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { withOrchestration } from '../utils/start-helper.js';
import { orchestrationFns } from './sendAnywhereFlows.js';
import * as flows from './sendAnywhere.flows.js';
Copy link
Member

Choose a reason for hiding this comment

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

👍

import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js';

/**
Expand Down Expand Up @@ -76,7 +76,7 @@ const contract = async (
);

// orchestrate uses the names on orchestrationFns to do a "prepare" of the associated behavior
const orchFns = orchestrateAll(orchestrationFns, {
const orchFns = orchestrateAll(flows, {
Copy link
Member

Choose a reason for hiding this comment

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

👍

zcf,
contractState,
localTransfer: zoeTools.localTransfer,
Expand Down
59 changes: 59 additions & 0 deletions packages/orchestration/src/examples/sendAnywhere.flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { M, mustMatch } from '@endo/patterns';

/**
* @import {GuestOf} from '@agoric/async-flow';
* @import {VBankAssetDetail} from '@agoric/vats/tools/board-utils.js';
* @import {ZoeTools} from '../utils/zoe-tools.js';
* @import {Orchestrator, LocalAccountMethods, OrchestrationAccountI, OrchestrationFlow} from '../types.js';
*/

const { entries } = Object;

// in guest file (the orchestration functions)
// the second argument is all the endowments provided

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {object} ctx
* @param {{ account?: OrchestrationAccountI & LocalAccountMethods }} ctx.contractState
* @param {GuestOf<ZoeTools['localTransfer']>} ctx.localTransfer
* @param {(brand: Brand) => Promise<VBankAssetDetail>} ctx.findBrandInVBank
* @param {ZCFSeat} seat
* @param {{ chainName: string; destAddr: string }} offerArgs
*/
export async function sendIt(
orch,
{ contractState, localTransfer, findBrandInVBank },
seat,
offerArgs,
) {
mustMatch(offerArgs, harden({ chainName: M.scalar(), destAddr: M.string() }));
const { chainName, destAddr } = offerArgs;
// NOTE the proposal shape ensures that the `give` is a single asset
const { give } = seat.getProposal();
const [[_kw, amt]] = entries(give);
const { denom } = await findBrandInVBank(amt.brand);
const chain = await orch.getChain(chainName);

if (!contractState.account) {
const agoricChain = await orch.getChain('agoric');
contractState.account = await agoricChain.makeAccount();
}

const info = await chain.getChainInfo();
const { chainId } = info;
assert(typeof chainId === 'string', 'bad chainId');

await localTransfer(seat, contractState.account, give);

await contractState.account.transfer(
{ denom, value: amt.value },
{
value: destAddr,
encoding: 'bech32',
chainId,
},
);
}
harden(sendIt);
62 changes: 0 additions & 62 deletions packages/orchestration/src/examples/sendAnywhereFlows.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { withOrchestration } from '../utils/start-helper.js';

/**
* @import {LocalTransfer} from '../utils/zoe-tools.js';
* @import {Orchestrator, CosmosValidatorAddress} from '../types.js'
* @import {Orchestrator, CosmosValidatorAddress, OrchestrationFlow} from '../types.js'
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {Remote} from '@agoric/internal';
Expand All @@ -17,6 +17,7 @@ import { withOrchestration } from '../utils/start-helper.js';
*/

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {object} ctx
* @param {LocalTransfer} ctx.localTransfer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { M } from '@endo/patterns';
import { withOrchestration } from '../utils/start-helper.js';

/**
* @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js'
* @import {Orchestrator, OrchestrationFlow} from '../types.js'
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {NameHub} from '@agoric/vats';
Expand All @@ -13,6 +13,7 @@ import { withOrchestration } from '../utils/start-helper.js';
*/

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {object} ctx
* @param {ZCF} ctx.zcf
Expand Down
23 changes: 7 additions & 16 deletions packages/orchestration/src/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { assertAllDefined } from '@agoric/internal';
* @import {HostOrchestrator} from './exos/orchestrator.js';
* @import {Remote} from '@agoric/internal';
* @import {CosmosInterchainService} from './exos/cosmos-interchain-service.js';
* @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from './types.js';
* @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, OrchestrationFlow, Orchestrator} from './types.js';
*/

/**
* For a given guest passed to orchestrate(), return the host-side form.
*
* @template {(orc: Orchestrator, ctx: any, ...args: any[]) => Promise<any>} GF
* @template {OrchestrationFlow} GF
* @typedef {GF extends (
* orc: Orchestrator,
* ctx: any,
Expand Down Expand Up @@ -66,18 +66,13 @@ export const makeOrchestrationFacade = ({
const { prepareEndowment, asyncFlow, adminAsyncFlow } = asyncFlowTools;

/**
* @template GR - return type
* @template HC - host context
* @template {any[]} GA - guest args
* @template {OrchestrationFlow<GuestInterface<HC>>} GF guest fn
* @param {string} durableName - the orchestration flow identity in the zone
* (to resume across upgrades)
* @param {HC} hostCtx - values to pass through the async flow membrane
* @param {(
* guestOrc: Orchestrator,
* guestCtx: GuestInterface<HC>,
* ...args: GA
* ) => Promise<GR>} guestFn
* @returns {(...args: HostArgs<GA>) => Vow<GR>}
* @param {GF} guestFn
* @returns {HostForGuest<GF>}
*/
const orchestrate = (durableName, hostCtx, guestFn) => {
const subZone = zone.subZone(durableName);
Expand All @@ -92,7 +87,7 @@ export const makeOrchestrationFacade = ({
const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn);

// cast because return could be arbitrary subtype
const orcFn = /** @type {(...args: HostArgs<GA>) => Vow<GR>} */ (
const orcFn = /** @type {HostForGuest<GF>} */ (
(...args) => hostFn(wrappedOrc, wrappedCtx, ...args)
);

Expand All @@ -106,11 +101,7 @@ export const makeOrchestrationFacade = ({
*
* @template HC - host context
* @template {{
* [durableName: string]: (
* orc: Orchestrator,
* ctx: GuestInterface<HC>,
* ...args: any[]
* ) => Promise<any>;
* [durableName: string]: OrchestrationFlow<GuestInterface<HC>>;
* }} GFM
* guest fn map
* @param {GFM} guestFns
Expand Down
5 changes: 5 additions & 0 deletions packages/orchestration/src/orchestration-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { CurrentWalletRecord } from '@agoric/smart-wallet/src/smartWallet.j
import type { Timestamp } from '@agoric/time';
import type { LocalChainAccount } from '@agoric/vats/src/localchain.js';
import type { ResolvedPublicTopic } from '@agoric/zoe/src/contractSupport/topics.js';
import type { Passable } from '@endo/marshal';
import type {
ChainInfo,
CosmosChainAccountMethods,
Expand Down Expand Up @@ -192,6 +193,10 @@ export interface OrchestrationAccountI {
getPublicTopics: () => Promise<Record<string, ResolvedPublicTopic<unknown>>>;
}

export interface OrchestrationFlow<CT = unknown> {
(orc: Orchestrator, ctx: CT, ...args: Passable[]): Promise<unknown>;
}

/**
* Internal structure for TransferMsgs.
* The type must be able to express transfers across different chains and transports.
Expand Down
25 changes: 25 additions & 0 deletions packages/orchestration/test/examples/bad.flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @file Module with linting errors, to verify the linting config detects them.
Copy link
Member

Choose a reason for hiding this comment

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

👏

* This assumes `reportUnusedDisableDirectives` is enabled in the local
* config.
*/

// TODO error on exports that:
// - aren't hardened (probably a new rule in @endo/eslint-plugin )
// - don't satisfy orchestration flow type

// eslint-disable-next-line no-restricted-syntax -- intentional for test
import { E } from '@endo/far';

export function notFlow() {
console.log('This function is not a flow');
}

export async function notHardened() {
console.log('This function is the most minimal flow, but it’s not hardened');
}

export async function usesE(orch, { someEref }) {
// eslint-disable-next-line no-restricted-syntax -- intentional for test
await E(someEref).foo();
}
Loading
Loading