Skip to content

Commit

Permalink
feat(orchestration): bind port for queries
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Apr 10, 2024
1 parent 102cbcc commit ce019fa
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 17 deletions.
35 changes: 35 additions & 0 deletions packages/boot/test/bootstrapTests/test-vat-orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { decodeBase64 } from '@endo/base64';
import { M, matches } from '@endo/patterns';
import { txToBase64 } from '@agoric/orchestration';
import { QueryBalanceRequest } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js';
import { makeWalletFactoryContext } from './walletFactory.ts';

const makeTestContext = async (t: ExecutionContext) =>
Expand Down Expand Up @@ -168,3 +169,37 @@ test('ICA connection can send msg with proto3', async t => {
message: 'ABCI code: 5: error handling packet: see events for details',
});
});

test('Query connection can be created', async t => {
const {
runUtils: { EV },
} = t.context;

const orchestration = await EV.vat('bootstrap').consumeItem('orchestration');

const account = await EV(orchestration).createQueryConnection('connection-0');
t.log('Query Account', account);
t.truthy(account, 'createAccount returns an account');
});

test('Query connection can send a query', async t => {
const {
runUtils: { EV },
} = t.context;

const orchestration = await EV.vat('bootstrap').consumeItem('orchestration');

// XXX should not return a "ChainAccount"
const account = await EV(orchestration).createQueryConnection('connection-0');

const queryMsg = txToBase64(
QueryBalanceRequest.toProtoMsg({
address: 'cosmos1test',
denom: 'uatom',
}),
);
// XXX implement QueryBalanceRequest handler and QueryBalanceResponse return mock
await t.throwsAsync(EV(account).executeEncodedTx(harden([queryMsg])), {
message: 'ABCI code: 5: error handling packet: see events for details',
});
});
4 changes: 4 additions & 0 deletions packages/cosmic-proto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
"./cosmos/staking/v1beta1/tx.js": {
"types": "./dist/codegen/cosmos/staking/v1beta1/tx.d.ts",
"default": "./dist/codegen/cosmos/staking/v1beta1/tx.js"
},
"./cosmos/bank/v1beta1/query.js": {
"types": "./dist/codegen/cosmos/bank/v1beta1/query.d.ts",
"default": "./dist/codegen/cosmos/bank/v1beta1/query.js"
}
},
"main": "dist/index.js",
Expand Down
66 changes: 58 additions & 8 deletions packages/orchestration/src/orchestration.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { NonNullish } from '@agoric/assert';
import { makeTracer } from '@agoric/internal';
import { V as E } from '@agoric/vat-data/vow.js';
import { M } from '@endo/patterns';
import { makeICAConnectionAddress, parseAddress } from './utils/address.js';
import { makeTxPacket, parsePacketAck } from './utils/tx.js';
import {
makeICAChannelAddress,
makeICQChannelAddress,
parseAddress,
} from './utils/address.js';
import '@agoric/network/exported.js';

/**
Expand Down Expand Up @@ -179,8 +183,27 @@ export const OrchestrationI = M.interface('Orchestration', {
createAccount: M.callWhen(M.string(), M.string()).returns(
M.remotable('ChainAccount'),
),
createQueryConnection: M.callWhen(M.string()).returns(
M.remotable('Connection'),
),
});

/** @typedef {{ icqControllerNonce: number; icaControllerNonce: number; }} PortNonceCounters */
/** @typedef {{ powers: PowerStore; queryPort: Port | undefined; } & PortNonceCounters } OrchestrationState */

// XXX temporary until #9165 (vat-network tracks nonces)
/** @type {Record<string, {portType: string; nonceKey: keyof PortNonceCounters}>} */
const portOpts = {
ica: {
portType: 'icacontroller',
nonceKey: 'icaControllerNonce',
},
icq: {
portType: 'icqcontroller',
nonceKey: 'icqControllerNonce',
},
};

/**
* @param {Zone} zone
* @param {ReturnType<typeof prepareChainAccount>} createChainAccount
Expand All @@ -190,7 +213,7 @@ const prepareOrchestration = (zone, createChainAccount) =>
'Orchestration',
{
self: M.interface('OrchestrationSelf', {
bindPort: M.callWhen().returns(M.remotable()),
bindPort: M.callWhen(M.record()).returns(M.remotable()),
}),
public: OrchestrationI,
},
Expand All @@ -203,16 +226,25 @@ const prepareOrchestration = (zone, createChainAccount) =>
powers.init(/** @type {keyof OrchestrationPowers} */ (name), power);
}
}
return { powers, icaControllerNonce: 0 };
return {
powers,
queryPort: undefined,
icqControllerNonce: 0,
icaControllerNonce: 0,
};
},
{
self: {
async bindPort() {
/**
* @param {{portType: string; nonceKey: string;}} opts
*/
async bindPort(opts) {
const network = getPower(this.state.powers, 'network');
const nonce = this.state[opts.nonceKey];
const port = await E(network).bindPort(
`/ibc-port/icacontroller-${this.state.icaControllerNonce}`,
`/ibc-port/${opts.portType}-${nonce}`,
);
this.state.icaControllerNonce += 1;
this.state[opts.nonceKey] += 1;
return port;
},
},
Expand All @@ -225,9 +257,9 @@ const prepareOrchestration = (zone, createChainAccount) =>
* @returns {Promise<ChainAccount>}
*/
async createAccount(hostConnectionId, controllerConnectionId) {
const port = await this.facets.self.bindPort();
const port = await this.facets.self.bindPort(portOpts.ica);

const remoteConnAddr = makeICAConnectionAddress(
const remoteConnAddr = makeICAChannelAddress(
hostConnectionId,
controllerConnectionId,
);
Expand All @@ -237,6 +269,24 @@ const prepareOrchestration = (zone, createChainAccount) =>
await E(port).connect(remoteConnAddr, chainAccount.connectionHandler);
// XXX if we fail, should we close the port (if it was created in this flow)?

return chainAccount.account;
},
/**
*
* @param {IBCConnectionID} controllerConnectionId
* self connection_id
* @returns {Promise<ChainAccount>}
*/
async createQueryConnection(controllerConnectionId) {
const port = await this.facets.self.bindPort(portOpts.icq);

const remoteConnAddr = makeICQChannelAddress(controllerConnectionId);
// XXX make a different interface. this works for now since it's also just a Connection
const chainAccount = createChainAccount(port, remoteConnAddr);

// await so we do not return a ChainAccount before it successfully instantiates
await E(port).connect(remoteConnAddr, chainAccount.connectionHandler);

return chainAccount.account;
},
},
Expand Down
16 changes: 15 additions & 1 deletion packages/orchestration/src/utils/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Fail } from '@agoric/assert';
* @param {string} [opts.txType] - default is `sdk_multi_msg`
* @param {string} [opts.version] - default is `ics27-1`
*/
export const makeICAConnectionAddress = (
export const makeICAChannelAddress = (
hostConnectionId,
controllerConnectionId,
{
Expand All @@ -34,6 +34,20 @@ export const makeICAConnectionAddress = (
});
return `/ibc-hop/${controllerConnectionId}/ibc-port/icahost/${ordering}/${connString}`;
};
harden(makeICAChannelAddress);

/**
* @param {IBCConnectionID} controllerConnectionId
* @param {{ version?: string }} [opts]
*/
export const makeICQChannelAddress = (
controllerConnectionId,
{ version = 'icq-1' } = {},
) => {
controllerConnectionId || Fail`controllerConnectionId is required`;
return `/ibc-hop/${controllerConnectionId}/ibc-port/icqhost/unordered/${version}`;
};
harden(makeICQChannelAddress);

/**
* Parse a chain address from a remote address string.
Expand Down
16 changes: 8 additions & 8 deletions packages/orchestration/test/utils/address.test.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import test from '@endo/ses-ava/prepare-endo.js';
import {
makeICAConnectionAddress,
makeICAChannelAddress,
parseAddress,
} from '../../src/utils/address.js';

test('makeICAConnectionAddress', t => {
t.throws(() => makeICAConnectionAddress(), {
test('makeICAChannelAddress', t => {
t.throws(() => makeICAChannelAddress(), {
message: 'hostConnectionId is required',
});
t.throws(() => makeICAConnectionAddress('connection-0'), {
t.throws(() => makeICAChannelAddress('connection-0'), {
message: 'controllerConnectionId is required',
});
t.is(
makeICAConnectionAddress('connection-1', 'connection-0'),
makeICAChannelAddress('connection-1', 'connection-0'),
'/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}',
'returns connection string when controllerConnectionId and hostConnectionId are provided',
);
t.is(
makeICAConnectionAddress('connection-1', 'connection-0', {
makeICAChannelAddress('connection-1', 'connection-0', {
version: 'ics27-0',
}),
'/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-0","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}',
'accepts custom version',
);
t.is(
makeICAConnectionAddress('connection-1', 'connection-0', {
makeICAChannelAddress('connection-1', 'connection-0', {
encoding: 'test',
}),
'/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"test","txType":"sdk_multi_msg"}',
'accepts custom encoding',
);
t.is(
makeICAConnectionAddress('connection-1', 'connection-0', {
makeICAChannelAddress('connection-1', 'connection-0', {
ordering: 'unordered',
}),
'/ibc-hop/connection-0/ibc-port/icahost/unordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}',
Expand Down

0 comments on commit ce019fa

Please sign in to comment.