From ce019fa8f25d71ed1a448f1415ce950c4e0c7740 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 10 Apr 2024 13:35:44 -0400 Subject: [PATCH] feat(orchestration): bind port for queries --- .../bootstrapTests/test-vat-orchestration.ts | 35 ++++++++++ packages/cosmic-proto/package.json | 4 ++ packages/orchestration/src/orchestration.js | 66 ++++++++++++++++--- packages/orchestration/src/utils/address.js | 16 ++++- .../orchestration/test/utils/address.test.js | 16 ++--- 5 files changed, 120 insertions(+), 17 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index 8f96c46e1ebb..7ef6bfb0dffe 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -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) => @@ -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', + }); +}); diff --git a/packages/cosmic-proto/package.json b/packages/cosmic-proto/package.json index d38b04eb45be..592fb91ab6da 100644 --- a/packages/cosmic-proto/package.json +++ b/packages/cosmic-proto/package.json @@ -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", diff --git a/packages/orchestration/src/orchestration.js b/packages/orchestration/src/orchestration.js index c7dd87f27254..87e0eb36a615 100644 --- a/packages/orchestration/src/orchestration.js +++ b/packages/orchestration/src/orchestration.js @@ -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'; /** @@ -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} */ +const portOpts = { + ica: { + portType: 'icacontroller', + nonceKey: 'icaControllerNonce', + }, + icq: { + portType: 'icqcontroller', + nonceKey: 'icqControllerNonce', + }, +}; + /** * @param {Zone} zone * @param {ReturnType} createChainAccount @@ -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, }, @@ -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; }, }, @@ -225,9 +257,9 @@ const prepareOrchestration = (zone, createChainAccount) => * @returns {Promise} */ 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, ); @@ -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} + */ + 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; }, }, diff --git a/packages/orchestration/src/utils/address.js b/packages/orchestration/src/utils/address.js index c5412d896126..376ba08dfe39 100644 --- a/packages/orchestration/src/utils/address.js +++ b/packages/orchestration/src/utils/address.js @@ -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, { @@ -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. diff --git a/packages/orchestration/test/utils/address.test.js b/packages/orchestration/test/utils/address.test.js index 9f30dfeb221c..80b13e1b7b59 100644 --- a/packages/orchestration/test/utils/address.test.js +++ b/packages/orchestration/test/utils/address.test.js @@ -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"}',