@agoric/orchestration 0.1.1-dev-a477bee.0 → 0.1.1-dev-2b7439e.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  // eslint-disable-next-line import/export
2
2
  export * from './src/types.js';
3
+ export * from './src/utils/address.js';
4
+ export * from './src/orchestration.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/orchestration",
3
- "version": "0.1.1-dev-a477bee.0+a477bee",
3
+ "version": "0.1.1-dev-2b7439e.0+2b7439e",
4
4
  "description": "Chain abstraction for Agoric's orchestration clients",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -29,13 +29,16 @@
29
29
  },
30
30
  "homepage": "https://github.com/Agoric/agoric-sdk#readme",
31
31
  "dependencies": {
32
- "@agoric/ertp": "0.16.3-dev-a477bee.0+a477bee",
33
- "@agoric/internal": "0.3.3-dev-a477bee.0+a477bee",
34
- "@agoric/notifier": "0.6.3-dev-a477bee.0+a477bee",
35
- "@agoric/vat-data": "0.5.3-dev-a477bee.0+a477bee",
36
- "@agoric/vats": "0.15.2-dev-a477bee.0+a477bee",
37
- "@agoric/zoe": "0.26.3-dev-a477bee.0+a477bee",
38
- "@agoric/zone": "0.2.3-dev-a477bee.0+a477bee",
32
+ "@agoric/assert": "0.6.1-dev-2b7439e.0+2b7439e",
33
+ "@agoric/ertp": "0.16.3-dev-2b7439e.0+2b7439e",
34
+ "@agoric/internal": "0.3.3-dev-2b7439e.0+2b7439e",
35
+ "@agoric/network": "0.1.1-dev-2b7439e.0+2b7439e",
36
+ "@agoric/notifier": "0.6.3-dev-2b7439e.0+2b7439e",
37
+ "@agoric/store": "0.9.3-dev-2b7439e.0+2b7439e",
38
+ "@agoric/vat-data": "0.5.3-dev-2b7439e.0+2b7439e",
39
+ "@agoric/vats": "0.15.2-dev-2b7439e.0+2b7439e",
40
+ "@agoric/zoe": "0.26.3-dev-2b7439e.0+2b7439e",
41
+ "@agoric/zone": "0.2.3-dev-2b7439e.0+2b7439e",
39
42
  "@endo/far": "^1.1.0",
40
43
  "@endo/marshal": "^1.4.0",
41
44
  "@endo/patterns": "^1.3.0"
@@ -43,7 +46,8 @@
43
46
  "devDependencies": {
44
47
  "@cosmjs/amino": "^0.32.3",
45
48
  "@cosmjs/proto-signing": "^0.32.3",
46
- "ava": "^5.3.0",
49
+ "@endo/ses-ava": "^1.2.0",
50
+ "ava": "^5.3.1",
47
51
  "cosmjs-types": "^0.9.0"
48
52
  },
49
53
  "ava": {
@@ -76,5 +80,5 @@
76
80
  "typeCoverage": {
77
81
  "atLeast": 96.96
78
82
  },
79
- "gitHead": "a477bee3be6e0e98b9346c0640380e276cf8423e"
83
+ "gitHead": "2b7439e3ad174ead924c942b105a4ad348d2bea9"
80
84
  }
@@ -0,0 +1,52 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Example contract that uses orchestration
4
+ */
5
+
6
+ import { makeDurableZone } from '@agoric/zone/durable.js';
7
+ import { V as E } from '@agoric/vat-data/vow.js';
8
+ import { M } from '@endo/patterns';
9
+
10
+ /**
11
+ * @import * as orchestration from '../types'
12
+ * @import * as vatData from '@agoric/vat-data'
13
+ */
14
+
15
+ /**
16
+ * @typedef {{
17
+ * hostConnectionId: orchestration.ConnectionId;
18
+ * controllerConnectionId: orchestration.ConnectionId;
19
+ * }} StakeAtomTerms
20
+ */
21
+
22
+ /**
23
+ *
24
+ * @param {ZCF<StakeAtomTerms>} zcf
25
+ * @param {{
26
+ * orchestration: orchestration.Orchestration;
27
+ * }} privateArgs
28
+ * @param {vatData.Baggage} baggage
29
+ */
30
+ export const start = async (zcf, privateArgs, baggage) => {
31
+ const { hostConnectionId, controllerConnectionId } = zcf.getTerms();
32
+ const { orchestration } = privateArgs;
33
+
34
+ const zone = makeDurableZone(baggage);
35
+
36
+ const publicFacet = zone.exo(
37
+ 'StakeAtom',
38
+ M.interface('StakeAtomI', {
39
+ createAccount: M.callWhen().returns(M.remotable('ChainAccount')),
40
+ }),
41
+ {
42
+ async createAccount() {
43
+ return E(orchestration).createAccount(
44
+ hostConnectionId,
45
+ controllerConnectionId,
46
+ );
47
+ },
48
+ },
49
+ );
50
+
51
+ return { publicFacet };
52
+ };
@@ -0,0 +1,240 @@
1
+ // @ts-check
2
+ /** @file Orchestration service */
3
+ import { NonNullish } from '@agoric/assert';
4
+ import { makeTracer } from '@agoric/internal';
5
+ import { V as E } from '@agoric/vat-data/vow.js';
6
+ import { M } from '@endo/patterns';
7
+ import { makeICAConnectionAddress, parseAddress } from './utils/address.js';
8
+ import '@agoric/network/exported.js';
9
+
10
+ /**
11
+ * @import { ConnectionId } from './types';
12
+ * @import { Zone } from '@agoric/base-zone';
13
+ */
14
+
15
+ const { Fail, bare } = assert;
16
+ const trace = makeTracer('Orchestration');
17
+
18
+ // TODO improve me
19
+ /** @typedef {string} ChainAddress */
20
+
21
+ /**
22
+ * @typedef {object} OrchestrationPowers
23
+ * @property {ERef<
24
+ * import('@agoric/orchestration/src/types').AttenuatedNetwork
25
+ * >} network
26
+ */
27
+
28
+ /**
29
+ * PowerStore is used so additional powers can be added on upgrade. See
30
+ * [#7337](https://github.com/Agoric/agoric-sdk/issues/7337) for tracking on Exo
31
+ * state migrations.
32
+ *
33
+ * @typedef {MapStore<
34
+ * keyof OrchestrationPowers,
35
+ * OrchestrationPowers[keyof OrchestrationPowers]
36
+ * >} PowerStore
37
+ */
38
+
39
+ /**
40
+ * @template {keyof OrchestrationPowers} K
41
+ * @param {PowerStore} powers
42
+ * @param {K} name
43
+ */
44
+ const getPower = (powers, name) => {
45
+ powers.has(name) || Fail`need powers.${bare(name)} for this method`;
46
+ return /** @type {OrchestrationPowers[K]} */ (powers.get(name));
47
+ };
48
+
49
+ export const ChainAccountI = M.interface('ChainAccount', {
50
+ getAccountAddress: M.call().returns(M.string()),
51
+ getLocalAddress: M.call().returns(M.string()),
52
+ getRemoteAddress: M.call().returns(M.string()),
53
+ getPort: M.call().returns(M.remotable('Port')),
54
+ close: M.callWhen().returns(M.string()),
55
+ });
56
+
57
+ export const ConnectionHandlerI = M.interface('ConnectionHandler', {
58
+ onOpen: M.callWhen(M.any(), M.string(), M.string(), M.any()).returns(M.any()),
59
+ onClose: M.callWhen(M.any(), M.any(), M.any()).returns(M.any()),
60
+ onReceive: M.callWhen(M.any(), M.string()).returns(M.any()),
61
+ });
62
+
63
+ /** @param {Zone} zone */
64
+ const prepareChainAccount = zone =>
65
+ zone.exoClassKit(
66
+ 'ChainAccount',
67
+ { account: ChainAccountI, connectionHandler: ConnectionHandlerI },
68
+ /**
69
+ * @param {Port} port
70
+ * @param {string} requestedRemoteAddress
71
+ */
72
+ (port, requestedRemoteAddress) =>
73
+ /**
74
+ * @type {{
75
+ * port: Port;
76
+ * connection: Connection | undefined;
77
+ * localAddress: string | undefined;
78
+ * requestedRemoteAddress: string;
79
+ * remoteAddress: string | undefined;
80
+ * accountAddress: ChainAddress | undefined;
81
+ * }}
82
+ */ (
83
+ harden({
84
+ port,
85
+ connection: undefined,
86
+ requestedRemoteAddress,
87
+ remoteAddress: undefined,
88
+ accountAddress: undefined,
89
+ localAddress: undefined,
90
+ })
91
+ ),
92
+ {
93
+ account: {
94
+ getAccountAddress() {
95
+ return NonNullish(
96
+ this.state.accountAddress,
97
+ 'Error parsing account address from remote address',
98
+ );
99
+ },
100
+ getLocalAddress() {
101
+ return NonNullish(
102
+ this.state.localAddress,
103
+ 'local address not available',
104
+ );
105
+ },
106
+ getRemoteAddress() {
107
+ return NonNullish(
108
+ this.state.remoteAddress,
109
+ 'remote address not available',
110
+ );
111
+ },
112
+ getPort() {
113
+ return this.state.port;
114
+ },
115
+ async close() {
116
+ /// XXX what should the behavior be here? and `onClose`?
117
+ // - retrieve assets?
118
+ // - revoke the port?
119
+ const { connection } = this.state;
120
+ if (!connection) throw Fail`connection not available`;
121
+ await null;
122
+ try {
123
+ await E(connection).close();
124
+ } catch (e) {
125
+ throw Fail`Failed to close connection: ${e}`;
126
+ }
127
+ return 'Connection closed';
128
+ },
129
+ },
130
+ connectionHandler: {
131
+ /**
132
+ * @param {Connection} connection
133
+ * @param {string} localAddr
134
+ * @param {string} remoteAddr
135
+ */
136
+ async onOpen(connection, localAddr, remoteAddr) {
137
+ trace(`ICA Channel Opened for ${localAddr} at ${remoteAddr}`);
138
+ this.state.connection = connection;
139
+ this.state.remoteAddress = remoteAddr;
140
+ this.state.localAddress = localAddr;
141
+ // XXX parseAddress currently throws, should it return '' instead?
142
+ this.state.accountAddress = parseAddress(remoteAddr);
143
+ },
144
+ async onClose(_connection, reason) {
145
+ trace(`ICA Channel closed. Reason: ${reason}`);
146
+ // XXX handle connection closing
147
+ // XXX is there a scenario where a connection will unexpectedly close? _I think yes_
148
+ },
149
+ async onReceive(connection, bytes) {
150
+ trace(`ICA Channel onReceive`, connection, bytes);
151
+ return '';
152
+ },
153
+ },
154
+ },
155
+ );
156
+
157
+ export const OrchestrationI = M.interface('Orchestration', {
158
+ createAccount: M.callWhen(M.string(), M.string()).returns(
159
+ M.remotable('ChainAccount'),
160
+ ),
161
+ });
162
+
163
+ /**
164
+ * @param {Zone} zone
165
+ * @param {ReturnType<typeof prepareChainAccount>} createChainAccount
166
+ */
167
+ const prepareOrchestration = (zone, createChainAccount) =>
168
+ zone.exoClassKit(
169
+ 'Orchestration',
170
+ {
171
+ self: M.interface('OrchestrationSelf', {
172
+ bindPort: M.callWhen().returns(M.remotable()),
173
+ }),
174
+ public: OrchestrationI,
175
+ },
176
+ /** @param {Partial<OrchestrationPowers>} [initialPowers] */
177
+ initialPowers => {
178
+ /** @type {PowerStore} */
179
+ const powers = zone.detached().mapStore('PowerStore');
180
+ if (initialPowers) {
181
+ for (const [name, power] of Object.entries(initialPowers)) {
182
+ powers.init(/** @type {keyof OrchestrationPowers} */ (name), power);
183
+ }
184
+ }
185
+ return { powers, icaControllerNonce: 0 };
186
+ },
187
+ {
188
+ self: {
189
+ async bindPort() {
190
+ const network = getPower(this.state.powers, 'network');
191
+ const port = await E(network)
192
+ .bind(`/ibc-port/icacontroller-${this.state.icaControllerNonce}`)
193
+ .catch(e => Fail`Failed to bind port: ${e}`);
194
+ this.state.icaControllerNonce += 1;
195
+ return port;
196
+ },
197
+ },
198
+ public: {
199
+ /**
200
+ * @param {ConnectionId} hostConnectionId
201
+ * the counterparty connection_id
202
+ * @param {ConnectionId} controllerConnectionId
203
+ * self connection_id
204
+ * @returns {Promise<ChainAccount>}
205
+ */
206
+ async createAccount(hostConnectionId, controllerConnectionId) {
207
+ const port = await this.facets.self.bindPort();
208
+
209
+ const remoteConnAddr = makeICAConnectionAddress(
210
+ hostConnectionId,
211
+ controllerConnectionId,
212
+ );
213
+ const chainAccount = createChainAccount(port, remoteConnAddr);
214
+
215
+ // await so we do not return a ChainAccount before it successfully instantiates
216
+ await E(port)
217
+ .connect(remoteConnAddr, chainAccount.connectionHandler)
218
+ // XXX if we fail, should we close the port (if it was created in this flow)?
219
+ .catch(e => Fail`Failed to create ICA connection: ${bare(e)}`);
220
+
221
+ return chainAccount.account;
222
+ },
223
+ },
224
+ },
225
+ );
226
+
227
+ /** @param {Zone} zone */
228
+ export const prepareOrchestrationTools = zone => {
229
+ const createChainAccount = prepareChainAccount(zone);
230
+ const makeOrchestration = prepareOrchestration(zone, createChainAccount);
231
+
232
+ return harden({ makeOrchestration });
233
+ };
234
+ harden(prepareOrchestrationTools);
235
+
236
+ /** @typedef {ReturnType<ReturnType<typeof prepareChainAccount>>} ChainAccountKit */
237
+ /** @typedef {ChainAccountKit['account']} ChainAccount */
238
+ /** @typedef {ReturnType<typeof prepareOrchestrationTools>} OrchestrationTools */
239
+ /** @typedef {ReturnType<OrchestrationTools['makeOrchestration']>} OrchestrationKit */
240
+ /** @typedef {OrchestrationKit['public']} Orchestration */
@@ -0,0 +1,99 @@
1
+ // @ts-check
2
+ import { V as E } from '@agoric/vat-data/vow.js';
3
+ import { Far } from '@endo/far';
4
+
5
+ /** @import { AttenuatedNetwork, Orchestration, OrchestrationVat } from '../types' */
6
+
7
+ /**
8
+ * @param {BootstrapPowers & {
9
+ * consume: {
10
+ * loadCriticalVat: VatLoader<any>;
11
+ * networkVat: NetworkVat;
12
+ * };
13
+ * produce: {
14
+ * orchestration: Producer<any>;
15
+ * orchestrationKit: Producer<any>;
16
+ * orchestrationVat: Producer<any>;
17
+ * };
18
+ * }} powers
19
+ * @param {object} options
20
+ * @param {{ orchestrationRef: VatSourceRef }} options.options
21
+ *
22
+ * @typedef {{
23
+ * orchestration: ERef<OrchestrationVat>;
24
+ * }} OrchestrationVats
25
+ */
26
+ export const setupOrchestrationVat = async (
27
+ {
28
+ consume: { loadCriticalVat, networkVat },
29
+ produce: {
30
+ orchestrationVat,
31
+ orchestration,
32
+ orchestrationKit: orchestrationKitP,
33
+ },
34
+ },
35
+ options,
36
+ ) => {
37
+ const { orchestrationRef } = options.options;
38
+ /** @type {OrchestrationVats} */
39
+ const vats = {
40
+ orchestration: E(loadCriticalVat)('orchestration', orchestrationRef),
41
+ };
42
+ // don't proceed if loadCriticalVat fails
43
+ await Promise.all(Object.values(vats));
44
+
45
+ orchestrationVat.reset();
46
+ orchestrationVat.resolve(vats.orchestration);
47
+
48
+ await networkVat;
49
+ /** @type {AttenuatedNetwork} */
50
+ const network = Far('Attenuated Network', {
51
+ /** @param {string} localAddr */
52
+ async bind(localAddr) {
53
+ return E(networkVat).bind(localAddr);
54
+ },
55
+ });
56
+
57
+ const newOrchestrationKit = await E(vats.orchestration).makeOrchestration({
58
+ network,
59
+ });
60
+
61
+ orchestration.reset();
62
+ orchestration.resolve(newOrchestrationKit.public);
63
+ orchestrationKitP.reset();
64
+ orchestrationKitP.resolve(newOrchestrationKit);
65
+ };
66
+
67
+ /**
68
+ * @param {BootstrapPowers & {
69
+ * consume: {
70
+ * orchestration: Orchestration;
71
+ * };
72
+ * }} powers
73
+ * @param {object} _options
74
+ */
75
+ export const addOrchestrationToClient = async (
76
+ { consume: { client, orchestration } },
77
+ _options,
78
+ ) => {
79
+ return E(client).assignBundle([_a => ({ orchestration })]);
80
+ };
81
+
82
+ export const getManifestForOrchestration = (_powers, { orchestrationRef }) => ({
83
+ manifest: {
84
+ [setupOrchestrationVat.name]: {
85
+ consume: {
86
+ loadCriticalVat: true,
87
+ networkVat: true,
88
+ },
89
+ produce: {
90
+ orchestration: 'orchestration',
91
+ orchestrationKit: 'orchestration',
92
+ orchestrationVat: 'orchestration',
93
+ },
94
+ },
95
+ },
96
+ options: {
97
+ orchestrationRef,
98
+ },
99
+ });
@@ -0,0 +1,68 @@
1
+ // @ts-check
2
+ import { makeTracer } from '@agoric/internal';
3
+ import { E } from '@endo/far';
4
+
5
+ const trace = makeTracer('StartStakeAtom', true);
6
+
7
+ /**
8
+ * @param {BootstrapPowers & { installation: {consume: {stakeAtom: Installation<import('../contracts/stakeAtom.contract.js').start>}}}} powers
9
+ * @param {{options: import('../contracts/stakeAtom.contract.js').StakeAtomTerms}} options
10
+ */
11
+ export const startStakeAtom = async (
12
+ {
13
+ consume: { orchestration, startUpgradable },
14
+ installation: {
15
+ consume: { stakeAtom },
16
+ },
17
+ instance: {
18
+ produce: { stakeAtom: produceInstance },
19
+ },
20
+ },
21
+ { options: { hostConnectionId, controllerConnectionId } },
22
+ ) => {
23
+ trace('startStakeAtom', { hostConnectionId, controllerConnectionId });
24
+ await null;
25
+
26
+ /** @type {StartUpgradableOpts<import('../contracts/stakeAtom.contract.js').start>} */
27
+ const startOpts = {
28
+ label: 'stakeAtom',
29
+ installation: stakeAtom,
30
+ terms: {
31
+ hostConnectionId,
32
+ controllerConnectionId,
33
+ },
34
+ privateArgs: {
35
+ orchestration: await orchestration,
36
+ },
37
+ };
38
+
39
+ const { instance } = await E(startUpgradable)(startOpts);
40
+ produceInstance.resolve(instance);
41
+ };
42
+ harden(startStakeAtom);
43
+
44
+ export const getManifestForStakeAtom = (
45
+ { restoreRef },
46
+ { installKeys, ...options },
47
+ ) => {
48
+ return {
49
+ manifest: {
50
+ [startStakeAtom.name]: {
51
+ consume: {
52
+ orchestration: true,
53
+ startUpgradable: true,
54
+ },
55
+ installation: {
56
+ consume: { stakeAtom: true },
57
+ },
58
+ instance: {
59
+ produce: { stakeAtom: true },
60
+ },
61
+ },
62
+ },
63
+ installations: {
64
+ stakeAtom: restoreRef(installKeys.stakeAtom),
65
+ },
66
+ options,
67
+ };
68
+ };
package/src/types.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { RouterProtocol } from '@agoric/network/src/router';
2
+
3
+ export type ConnectionId = `connection-${number}`;
4
+
5
+ export type AttenuatedNetwork = Pick<RouterProtocol, 'bind'>;
6
+
7
+ export type * from './orchestration.js';
8
+ export type * from './vat-orchestration.js';
package/src/types.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import '@agoric/network/exported.js';
2
3
  import '@agoric/vats/exported.js';
3
4
  import '@agoric/zoe/exported.js';
4
5
 
@@ -0,0 +1,54 @@
1
+ // @ts-check
2
+ import { Fail } from '@agoric/assert';
3
+
4
+ /** @import { ConnectionId } from '../types'; */
5
+
6
+ /**
7
+ * @param {ConnectionId} hostConnectionId Counterpart Connection ID
8
+ * @param {ConnectionId} controllerConnectionId Self Connection ID
9
+ * @param {object} [opts]
10
+ * @param {string} [opts.encoding] - message encoding format for the channel. default is `proto3`
11
+ * @param {'ordered' | 'unordered'} [opts.ordering] - channel ordering. currently only `ordered` is supported for ics27-1
12
+ * @param {string} [opts.txType] - default is `sdk_multi_msg`
13
+ * @param {string} [opts.version] - default is `ics27-1`
14
+ */
15
+ export const makeICAConnectionAddress = (
16
+ hostConnectionId,
17
+ controllerConnectionId,
18
+ {
19
+ version = 'ics27-1',
20
+ encoding = 'proto3',
21
+ ordering = 'ordered',
22
+ txType = 'sdk_multi_msg',
23
+ } = {},
24
+ ) => {
25
+ hostConnectionId || Fail`hostConnectionId is required`;
26
+ controllerConnectionId || Fail`controllerConnectionId is required`;
27
+ const connString = JSON.stringify({
28
+ version,
29
+ controllerConnectionId,
30
+ hostConnectionId,
31
+ address: '', // will be provided by the counterparty after channelOpenAck
32
+ encoding,
33
+ txType,
34
+ });
35
+ return `/ibc-hop/${controllerConnectionId}/ibc-port/icahost/${ordering}/${connString}`;
36
+ };
37
+
38
+ /**
39
+ * Parse a chain address from a remote address string.
40
+ * Assumes the address string is in a JSON format and contains an "address" field.
41
+ * This function is designed to be safe against malformed inputs and unexpected data types, and will return `undefined` in those cases.
42
+ * @param {string} remoteAddressString - remote address string, including version
43
+ * @returns {string | undefined} returns undefined on error
44
+ */
45
+ export const parseAddress = remoteAddressString => {
46
+ try {
47
+ // Extract JSON version string assuming it's always surrounded by {}
48
+ const jsonStr = remoteAddressString?.match(/{.*?}/)?.[0];
49
+ const jsonObj = jsonStr ? JSON.parse(jsonStr) : undefined;
50
+ return jsonObj?.address ?? undefined;
51
+ } catch (error) {
52
+ return undefined;
53
+ }
54
+ };
@@ -0,0 +1,22 @@
1
+ // @ts-check
2
+ import { Far } from '@endo/far';
3
+ import { makeDurableZone } from '@agoric/zone/durable.js';
4
+ import { prepareOrchestrationTools } from './orchestration.js';
5
+
6
+ /** @import { OrchestrationPowers } from './types.js' */
7
+
8
+ export const buildRootObject = (_vatPowers, _args, baggage) => {
9
+ const zone = makeDurableZone(baggage);
10
+ const { makeOrchestration } = prepareOrchestrationTools(
11
+ zone.subZone('orchestration'),
12
+ );
13
+
14
+ return Far('OrchestrationVat', {
15
+ /** @param {Partial<OrchestrationPowers>} [initialPowers] */
16
+ makeOrchestration(initialPowers = {}) {
17
+ return makeOrchestration(initialPowers);
18
+ },
19
+ });
20
+ };
21
+
22
+ /** @typedef {ReturnType<typeof buildRootObject>} OrchestrationVat */