Skip to content

Commit

Permalink
refactor(orchestration): move chainAccountKit to its own file
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Apr 30, 2024
1 parent e538811 commit 770160d
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 163 deletions.
175 changes: 175 additions & 0 deletions packages/orchestration/src/exos/chainAccountKit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// @ts-check
/** @file Orchestration service */
import { NonNullish } from '@agoric/assert';
import { makeTracer } from '@agoric/internal';
import '@agoric/network/exported.js';
import { V as E } from '@agoric/vat-data/vow.js';
import { M } from '@endo/patterns';
import { PaymentShape, PurseShape } from '@agoric/ertp';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { ConnectionHandlerI } from '../typeGuards.js';
import { makeTxPacket, parseTxPacket } from '../utils/packet.js';
import { parseAddress } from '../utils/address.js';

/**
* @import { ChainAddress } from '../types.js';
* @import { Zone } from '@agoric/base-zone';
* @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
*/

/** @typedef {ReturnType<ReturnType<typeof prepareChainAccountKit>>} ChainAccountKit */

const { Fail } = assert;
const trace = makeTracer('ChainAccountKit');

/** @import {AnyJson} from '@agoric/cosmic-proto'; */

export const Proto3Shape = {
typeUrl: M.string(),
value: M.string(),
};

export const ChainAccountI = M.interface('ChainAccount', {
getAccountAddress: M.call().returns(M.string()),
getLocalAddress: M.call().returns(M.string()),
getRemoteAddress: M.call().returns(M.string()),
getPort: M.call().returns(M.remotable('Port')),
executeTx: M.call(M.arrayOf(M.record())).returns(M.promise()),
executeEncodedTx: M.call(M.arrayOf(Proto3Shape))
.optional(M.record())
.returns(M.promise()),
close: M.callWhen().returns(M.undefined()),
deposit: M.callWhen(PaymentShape).returns(M.undefined()),
getPurse: M.callWhen().returns(PurseShape),
prepareTransfer: M.callWhen().returns(InvitationShape),
});

/** @param {Zone} zone */
export const prepareChainAccountKit = zone =>
zone.exoClassKit(
'ChainAccount',
{ account: ChainAccountI, connectionHandler: ConnectionHandlerI },
/**
* @param {Port} port
* @param {string} requestedRemoteAddress
*/
(port, requestedRemoteAddress) =>
/**
* @type {{
* port: Port;
* connection: Remote<Connection> | undefined;
* localAddress: string | undefined;
* requestedRemoteAddress: string;
* remoteAddress: string | undefined;
* accountAddress: ChainAddress['address'] | undefined;
* }}
*/ (
harden({
port,
connection: undefined,
requestedRemoteAddress,
remoteAddress: undefined,
accountAddress: undefined,
localAddress: undefined,
})
),
{
account: {
/**
* @returns {string} the address of the account on the chain
*/
getAccountAddress() {
return NonNullish(
this.state.accountAddress,
'Error parsing account address from remote address',
);
},
getLocalAddress() {
return NonNullish(
this.state.localAddress,
'local address not available',
);
},
getRemoteAddress() {
return NonNullish(
this.state.remoteAddress,
'remote address not available',
);
},
getPort() {
return this.state.port;
},
executeTx() {
throw new Error('not yet implemented');
},
/**
* Submit a transaction on behalf of the remote account for execution on the remote chain.
* @param {AnyJson[]} msgs
* @param {Partial<Omit<TxBody, 'messages'>>} [opts]
* @returns {Promise<string>} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object.
* @throws {Error} if packet fails to send or an error is returned
*/
executeEncodedTx(msgs, opts) {
const { connection } = this.state;
if (!connection) throw Fail`connection not available`;
return E.when(
E(connection).send(makeTxPacket(msgs, opts)),
// if parseTxPacket cannot find a `result` key, it throws
ack => parseTxPacket(ack),
);
},
/**
* Close the remote account
*/
async close() {
/// XXX what should the behavior be here? and `onClose`?
// - retrieve assets?
// - revoke the port?
const { connection } = this.state;
if (!connection) throw Fail`connection not available`;
await E(connection).close();
},
async deposit(payment) {
console.log('deposit got', payment);
throw new Error('not yet implemented');
},
/**
* get Purse for a brand to .withdraw() a Payment from the account
* @param {Brand} brand
*/
async getPurse(brand) {
console.log('getPurse got', brand);
throw new Error('not yet implemented');
},

/* transfer account to new holder */
async prepareTransfer() {
throw new Error('not yet implemented');
},
},
connectionHandler: {
/**
* @param {Remote<Connection>} connection
* @param {string} localAddr
* @param {string} remoteAddr
*/
async onOpen(connection, localAddr, remoteAddr) {
trace(`ICA Channel Opened for ${localAddr} at ${remoteAddr}`);
this.state.connection = connection;
this.state.remoteAddress = remoteAddr;
this.state.localAddress = localAddr;
// XXX parseAddress currently throws, should it return '' instead?
this.state.accountAddress = parseAddress(remoteAddr);
},
async onClose(_connection, reason) {
trace(`ICA Channel closed. Reason: ${reason}`);
// XXX handle connection closing
// XXX is there a scenario where a connection will unexpectedly close? _I think yes_
},
async onReceive(connection, bytes) {
trace(`ICA Channel onReceive`, connection, bytes);
return '';
},
},
},
);
165 changes: 2 additions & 163 deletions packages/orchestration/src/service.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
// @ts-check
/** @file Orchestration service */
import { NonNullish } from '@agoric/assert';
import { makeTracer } from '@agoric/internal';
import '@agoric/network/exported.js';
import { V as E } from '@agoric/vat-data/vow.js';
import { M } from '@endo/patterns';
import { PaymentShape, PurseShape } from '@agoric/ertp';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { Shape as NetworkShape } from '@agoric/network';
import { ConnectionHandlerI } from './typeGuards.js';
import { prepareChainAccountKit } from './exos/chainAccountKit.js';
import { prepareQueryConnectionKit } from './exos/queryConnectionKit.js';
import { makeTxPacket, parseTxPacket } from './utils/packet.js';
import {
makeICAChannelAddress,
makeICQChannelAddress,
parseAddress,
} from './utils/address.js';

/**
* @import { QueryConnection, ChainAddress } from './types.js';
* @import { QueryConnection, ChainAccountKit } from './types.js';
* @import { Zone } from '@agoric/base-zone';
* @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
* @import { IBCConnectionID } from '@agoric/vats';
*/

const { Fail, bare } = assert;
const trace = makeTracer('Orchestration');

/** @import {AnyJson} from '@agoric/cosmic-proto'; */

/**
* @typedef {object} OrchestrationPowers
Expand Down Expand Up @@ -55,156 +45,6 @@ const getPower = (powers, name) => {
return /** @type {OrchestrationPowers[K]} */ (powers.get(name));
};

export const Proto3Shape = {
typeUrl: M.string(),
value: M.string(),
};

export const ChainAccountI = M.interface('ChainAccount', {
getAccountAddress: M.call().returns(M.string()),
getLocalAddress: M.call().returns(M.string()),
getRemoteAddress: M.call().returns(M.string()),
getPort: M.call().returns(M.remotable('Port')),
executeTx: M.call(M.arrayOf(M.record())).returns(M.promise()),
executeEncodedTx: M.call(M.arrayOf(Proto3Shape))
.optional(M.record())
.returns(M.promise()),
close: M.callWhen().returns(M.undefined()),
deposit: M.callWhen(PaymentShape).returns(M.undefined()),
getPurse: M.callWhen().returns(PurseShape),
prepareTransfer: M.callWhen().returns(InvitationShape),
});

/** @param {Zone} zone */
const prepareChainAccountKit = zone =>
zone.exoClassKit(
'ChainAccount',
{ account: ChainAccountI, connectionHandler: ConnectionHandlerI },
/**
* @param {Port} port
* @param {string} requestedRemoteAddress
*/
(port, requestedRemoteAddress) =>
/**
* @type {{
* port: Port;
* connection: Remote<Connection> | undefined;
* localAddress: string | undefined;
* requestedRemoteAddress: string;
* remoteAddress: string | undefined;
* accountAddress: ChainAddress['address'] | undefined;
* }}
*/ (
harden({
port,
connection: undefined,
requestedRemoteAddress,
remoteAddress: undefined,
accountAddress: undefined,
localAddress: undefined,
})
),
{
account: {
/**
* @returns {string} the address of the account on the chain
*/
getAccountAddress() {
return NonNullish(
this.state.accountAddress,
'Error parsing account address from remote address',
);
},
getLocalAddress() {
return NonNullish(
this.state.localAddress,
'local address not available',
);
},
getRemoteAddress() {
return NonNullish(
this.state.remoteAddress,
'remote address not available',
);
},
getPort() {
return this.state.port;
},
executeTx() {
throw new Error('not yet implemented');
},
/**
* Submit a transaction on behalf of the remote account for execution on the remote chain.
* @param {AnyJson[]} msgs
* @param {Partial<Omit<TxBody, 'messages'>>} [opts]
* @returns {Promise<string>} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object.
* @throws {Error} if packet fails to send or an error is returned
*/
executeEncodedTx(msgs, opts) {
const { connection } = this.state;
if (!connection) throw Fail`connection not available`;
return E.when(
E(connection).send(makeTxPacket(msgs, opts)),
// if parseTxPacket cannot find a `result` key, it throws
ack => parseTxPacket(ack),
);
},
/**
* Close the remote account
*/
async close() {
/// XXX what should the behavior be here? and `onClose`?
// - retrieve assets?
// - revoke the port?
const { connection } = this.state;
if (!connection) throw Fail`connection not available`;
await E(connection).close();
},
async deposit(payment) {
console.log('deposit got', payment);
throw new Error('not yet implemented');
},
/**
* get Purse for a brand to .withdraw() a Payment from the account
* @param {Brand} brand
*/
async getPurse(brand) {
console.log('getPurse got', brand);
throw new Error('not yet implemented');
},

/* transfer account to new holder */
async prepareTransfer() {
throw new Error('not yet implemented');
},
},
connectionHandler: {
/**
* @param {Remote<Connection>} connection
* @param {string} localAddr
* @param {string} remoteAddr
*/
async onOpen(connection, localAddr, remoteAddr) {
trace(`ICA Channel Opened for ${localAddr} at ${remoteAddr}`);
this.state.connection = connection;
this.state.remoteAddress = remoteAddr;
this.state.localAddress = localAddr;
// XXX parseAddress currently throws, should it return '' instead?
this.state.accountAddress = parseAddress(remoteAddr);
},
async onClose(_connection, reason) {
trace(`ICA Channel closed. Reason: ${reason}`);
// XXX handle connection closing
// XXX is there a scenario where a connection will unexpectedly close? _I think yes_
},
async onReceive(connection, bytes) {
trace(`ICA Channel onReceive`, connection, bytes);
return '';
},
},
},
);

export const OrchestrationI = M.interface('Orchestration', {
createAccount: M.callWhen(M.string(), M.string()).returns(
M.remotable('ChainAccount'),
Expand Down Expand Up @@ -321,7 +161,6 @@ export const prepareOrchestrationTools = zone => {
};
harden(prepareOrchestrationTools);

/** @typedef {ReturnType<ReturnType<typeof prepareChainAccountKit>>} ChainAccountKit */
/** @typedef {ReturnType<typeof prepareOrchestrationTools>} OrchestrationTools */
/** @typedef {ReturnType<OrchestrationTools['makeOrchestrationKit']>} OrchestrationKit */
/** @typedef {OrchestrationKit['public']} OrchestrationService */
1 change: 1 addition & 0 deletions packages/orchestration/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
export type * from './service.js';
export type * from './vat-orchestration.js';
export type * from './utils/packet.js';
export type * from './exos/chainAccountKit.js';
export type * from './exos/queryConnectionKit.js';

/**
Expand Down

0 comments on commit 770160d

Please sign in to comment.