From d15cb41b5dc7d3c47a1bc4c034ae74adfc7f838a Mon Sep 17 00:00:00 2001 From: Ikenna Omekam Date: Tue, 30 Apr 2024 14:29:18 -0400 Subject: [PATCH] Move port allocation in network vat to PortAllocator (#9228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes: https://github.com/Agoric/agoric-sdk/issues/9165 ## Description `bindPort` gives a caller too much power to specify exactly what port they want allocated. With this change, we create a central object, `PortAllocator` that handles handing out ports to the caller. As of today, we are allocating ports for: 1. `/ibc-port` 2. `/ibc-port/icacontroller` 3. `/ibc-port/pegasus` ### Security Considerations This will prevent `squatting`, where callers can come and claim certain ports that they have no association with ### Documentation Considerations We've removed the `bindPort` method from the network vat. All the contracts inside of `agoric-sdk` have been changed to use `portAllocator` ### Testing Considerations I've included some unit test testing the new allocate APIs --------- Co-authored-by: Michael FIG Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../boot/test/bootstrapTests/ibcClientMock.js | 8 +-- .../boot/test/bootstrapTests/ibcServerMock.js | 6 +- .../bootstrapTests/test-net-ibc-upgrade.ts | 6 +- .../test/bootstrapTests/test-vats-restart.ts | 33 --------- .../test/smartWallet/boot-test-utils.js | 1 - packages/network/README.md | 11 ++- packages/network/src/network.js | 72 +++++++++++++++++++ packages/network/src/types.js | 2 + packages/network/test/test-network-misc.js | 52 +++++++++++++- .../src/proposals/orchestration-proposal.js | 19 ++--- packages/orchestration/src/service.js | 15 ++-- packages/orchestration/src/types.d.ts | 2 - .../pegasus/src/proposals/core-proposal.js | 6 +- .../vats/src/proposals/network-proposal.js | 46 ++++++------ packages/vats/src/vat-network.js | 11 ++- packages/vats/test/test-network.js | 4 +- packages/vats/tools/boot-test-utils.js | 1 - 17 files changed, 188 insertions(+), 107 deletions(-) diff --git a/packages/boot/test/bootstrapTests/ibcClientMock.js b/packages/boot/test/bootstrapTests/ibcClientMock.js index 4614e0b49c9..33458da604e 100644 --- a/packages/boot/test/bootstrapTests/ibcClientMock.js +++ b/packages/boot/test/bootstrapTests/ibcClientMock.js @@ -6,14 +6,14 @@ import { V as E } from '@agoric/vat-data/vow.js'; /** * @param {ZCF} zcf * @param {{ - * address: string, - * networkVat: ERef>; + * portAllocator: ERef; * }} privateArgs * @param {import("@agoric/vat-data").Baggage} _baggage */ export const start = async (zcf, privateArgs, _baggage) => { - const { address, networkVat } = privateArgs; - const myPort = await E(networkVat).bindPort(address); + const { portAllocator } = privateArgs; + + const myPort = await E(portAllocator).allocateCustomIBCPort(); const { log } = console; let connP; diff --git a/packages/boot/test/bootstrapTests/ibcServerMock.js b/packages/boot/test/bootstrapTests/ibcServerMock.js index d56f316cb86..1370146c65e 100644 --- a/packages/boot/test/bootstrapTests/ibcServerMock.js +++ b/packages/boot/test/bootstrapTests/ibcServerMock.js @@ -12,14 +12,14 @@ const { log } = console; * @param {ZCF} zcf * @param {{ * address: string, - * networkVat: ERef>; + * portAllocator: ERef; * }} privateArgs * @param {import("@agoric/vat-data").Baggage} _baggage */ export const start = async (zcf, privateArgs, _baggage) => { - const { address, networkVat } = privateArgs; + const { portAllocator } = privateArgs; - const boundPort = await E(networkVat).bindPort(address); + const boundPort = await E(portAllocator).allocateCustomIBCPort(); /** @type {Array<[label: string, resolve: (value: any) => void, reject: (reason: any) => void]>} */ const queue = []; diff --git a/packages/boot/test/bootstrapTests/test-net-ibc-upgrade.ts b/packages/boot/test/bootstrapTests/test-net-ibc-upgrade.ts index bfb7079406d..dfe36d871ff 100644 --- a/packages/boot/test/bootstrapTests/test-net-ibc-upgrade.ts +++ b/packages/boot/test/bootstrapTests/test-net-ibc-upgrade.ts @@ -113,7 +113,7 @@ const upgradeVats = async (t, EV, vatsToUpgrade) => { test.serial('upgrade at many points in network API flow', async t => { const { installation } = t.context; const { EV } = t.context.runUtils; - const networkVat = await EV.vat('bootstrap').consumeItem('networkVat'); + const portAllocator = await EV.vat('bootstrap').consumeItem('portAllocator'); const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); const flow = entries({ @@ -122,7 +122,7 @@ test.serial('upgrade at many points in network API flow', async t => { installation.ibcServerMock, {}, {}, - { address: '/ibc-port/', networkVat }, + { portAllocator }, ); t.truthy(started.creatorFacet, `${label} ibcServerMock`); return [label, { server: started.creatorFacet }]; @@ -140,7 +140,7 @@ test.serial('upgrade at many points in network API flow', async t => { installation.ibcClientMock, {}, {}, - { address: '/ibc-port/', networkVat }, + { portAllocator }, ); t.truthy(started.creatorFacet, `${label} ibcClientMock`); return [label, { ...opts, client: started.creatorFacet }]; diff --git a/packages/boot/test/bootstrapTests/test-vats-restart.ts b/packages/boot/test/bootstrapTests/test-vats-restart.ts index 236ad99b1e7..bd1f3340fef 100644 --- a/packages/boot/test/bootstrapTests/test-vats-restart.ts +++ b/packages/boot/test/bootstrapTests/test-vats-restart.ts @@ -105,20 +105,6 @@ test.serial('run network vat proposal', async t => { t.pass(); // reached here without throws }); -test.serial('register network protocol before upgrade', async t => { - const { EV } = t.context.runUtils; - const net = await EV.vat('bootstrap').consumeItem('networkVat'); - const h1 = await EV(net).makeLoopbackProtocolHandler(); - - t.log('register P1'); - await EV(net).registerProtocolHandler(['P1'], h1); - - t.log('register P1 again? No.'); - await t.throwsAsync(EV(net).registerProtocolHandler(['P1'], h1), { - message: /key "P1" already registered/, - }); -}); - test.serial('make IBC callbacks before upgrade', async t => { const { EV } = t.context.runUtils; const vatStore = await EV.vat('bootstrap').consumeItem('vatStore'); @@ -155,25 +141,6 @@ test.serial('use IBC callbacks after upgrade', async t => { t.truthy(h.bridgeHandler, 'bridgeHandler'); }); -test.serial('networkVat registrations are durable', async t => { - const { EV } = t.context.runUtils; - const net = await EV.vat('bootstrap').consumeItem('networkVat'); - - const h2 = await EV(net).makeLoopbackProtocolHandler(); - t.log('register P1 again? No.'); - await t.throwsAsync(EV(net).registerProtocolHandler(['P1'], h2), { - message: /key "P1" already registered/, - }); - - t.log('IBC protocol handler already registered?'); - await t.throwsAsync( - EV(net).registerProtocolHandler(['/ibc-port', '/ibc-hop'], h2), - { - message: /key "\/ibc-port" already registered in collection "prefix"/, - }, - ); -}); - test.serial('read metrics', async t => { const { EV } = t.context.runUtils; diff --git a/packages/inter-protocol/test/smartWallet/boot-test-utils.js b/packages/inter-protocol/test/smartWallet/boot-test-utils.js index b222b0fdab3..1a924713a75 100644 --- a/packages/inter-protocol/test/smartWallet/boot-test-utils.js +++ b/packages/inter-protocol/test/smartWallet/boot-test-utils.js @@ -58,7 +58,6 @@ export const makeMock = log => network: Far('network', { registerProtocolHandler: noop, - bindPort: () => harden({ addListener: noop }), }), }, }); diff --git a/packages/network/README.md b/packages/network/README.md index f8e9e08a925..55d813e7612 100644 --- a/packages/network/README.md +++ b/packages/network/README.md @@ -57,15 +57,12 @@ E(home.ibcport[0]).connect(remoteEndpoint, connectionHandler) The other side of `connect()` is a "listening port". These ports are waiting for inbound connections to be established. -To get a listening port, you need a `NetworkInterface` object (such as the one on your `ag-solo` under `home.network`) and ask it to `bindPort()` to an endpoint. You can either provide a specific port name, or allow the API to allocate a random one for you. The endpoint specifies the type of connection that this port will be able to accept (IBC, TCP, etc), and some properties of that connection. `bindPort()` uses a "multiaddress" to encode this information. +To get a listening port, you need a `NetworkInterface` object (such as the one on your `ag-solo` under `home.network`) and ask it for a port, via the `PortAllocator`. ```js // ask for a random allocation - ends with a slash -E(home.network).bindPort('/ibc-port/') - .then(port => usePort(port)); - -// or ask for a specific port name -E(home.network).bindPort('/ibc-port/my-cool-port-name') +E(home.network).getPortAllocator() + .then(portAllocator => E(portAllocator).allocateCustomIBCPort()) .then(port => usePort(port)); ``` @@ -147,7 +144,7 @@ Note that if you want to listen on this port again, you can just call `port.addL ### Closing the Port Entirely -Removing a listener doesn't release the port address to make it available for other `bindPort()` requests. You can call: +Removing a listener doesn't release the port address to make it available for other `PortAllocator` requests. You can call: ```js port.revoke(); diff --git a/packages/network/src/network.js b/packages/network/src/network.js index 2404ff54ca6..2a09b56b33e 100644 --- a/packages/network/src/network.js +++ b/packages/network/src/network.js @@ -46,6 +46,20 @@ export function getPrefixes(addr) { return ret; } +/** + * Validate IBC port name + * @param {string} specifiedName + */ +function throwIfInvalidPortName(specifiedName) { + // Contains between 2 and 128 characters + // Can contain alphanumeric characters + // Valid symbols: ., ,, _, +, -, #, [, ], <, > + const portNameRegex = new RegExp('^[a-zA-Z0-9.,_+\\-#<>\\[\\]]{2,128}$'); + if (!portNameRegex.test(specifiedName)) { + throw new Error(`Invalid IBC port name: ${specifiedName}`); + } +} + /** * @typedef {object} ConnectionOpts * @property {Endpoint[]} addrs @@ -1426,3 +1440,61 @@ export function prepareLoopbackProtocolHandler(zone, { watch, allVows }) { return makeLoopbackProtocolHandler; } + +/** + * + * @param {import('@agoric/base-zone').Zone} zone + * @param {ReturnType} powers + */ +export const preparePortAllocator = (zone, { watch }) => + zone.exoClass( + 'PortAllocator', + M.interface('PortAllocator', { + allocateCustomIBCPort: M.callWhen() + .optional(M.string()) + .returns(Shape.Vow$(Shape.Port)), + allocateICAControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)), + allocateCustomLocalPort: M.callWhen() + .optional(M.string()) + .returns(Shape.Vow$(Shape.Port)), + }), + ({ protocol }) => ({ protocol, lastICAPortNum: 0n }), + { + allocateCustomIBCPort(specifiedName = '') { + const { state } = this; + let localAddr = `/ibc-port/`; + + if (specifiedName) { + throwIfInvalidPortName(specifiedName); + + localAddr = `/ibc-port/custom-${specifiedName}`; + } + + // Allocate an IBC port with a unique generated name. + return watch(E(state.protocol).bindPort(localAddr)); + }, + allocateICAControllerPort() { + const { state } = this; + state.lastICAPortNum += 1n; + return watch( + E(state.protocol).bindPort( + `/ibc-port/icacontroller-${state.lastICAPortNum}`, + ), + ); + }, + allocateCustomLocalPort(specifiedName = '') { + const { state } = this; + + let localAddr = `/local/`; + + if (specifiedName) { + throwIfInvalidPortName(specifiedName); + + localAddr = `/local/custom-${specifiedName}`; + } + + // Allocate a local port with a unique generated name. + return watch(E(state.protocol).bindPort(localAddr)); + }, + }, + ); diff --git a/packages/network/src/types.js b/packages/network/src/types.js index 04206a67d5f..43e0e90c366 100644 --- a/packages/network/src/types.js +++ b/packages/network/src/types.js @@ -188,3 +188,5 @@ * ) => PromiseVow} outbound * Create an outbound connection */ + +/** @typedef {ReturnType>} PortAllocator */ diff --git a/packages/network/test/test-network-misc.js b/packages/network/test/test-network-misc.js index 258b601d412..9c23e443b4d 100644 --- a/packages/network/test/test-network-misc.js +++ b/packages/network/test/test-network-misc.js @@ -14,6 +14,7 @@ import { prepareRouter, prepareLoopbackProtocolHandler, prepareNetworkProtocol, + preparePortAllocator, } from '../src/index.js'; import '../src/types.js'; @@ -53,7 +54,7 @@ const prepareProtocolHandler = ( }, async generatePortID() { this.state.nonce += 1; - return `${this.state.nonce}`; + return `port-${this.state.nonce}`; }, async onBind(port, localAddr) { t.assert(port, `port is supplied to onBind`); @@ -167,6 +168,55 @@ test('handled protocol', async t => { await when(port.revoke()); }); +test('verify port allocation', async t => { + const zone = makeDurableZone( + provideBaggage('network-verify-port-allocation'), + ); + const powers = prepareVowTools(zone); + const { when } = powers; + const makeNetworkProtocol = prepareNetworkProtocol(zone, powers); + const makeEchoConnectionHandler = prepareEchoConnectionKit(zone); + const makeProtocolHandler = prepareProtocolHandler( + zone, + t, + makeEchoConnectionHandler, + powers, + ); + const protocol = makeNetworkProtocol(makeProtocolHandler()); + const makePortAllocator = preparePortAllocator(zone, powers); + const portAllocator = makePortAllocator({ protocol }); + + const ibcPort = await when(portAllocator.allocateCustomIBCPort()); + t.is(ibcPort.getLocalAddress(), '/ibc-port/port-1'); + + const namedIbcPort = await when( + portAllocator.allocateCustomIBCPort('test-1'), + ); + t.is(namedIbcPort.getLocalAddress(), '/ibc-port/custom-test-1'); + + const icaControllerPort1 = await when( + portAllocator.allocateICAControllerPort(), + ); + t.is(icaControllerPort1.getLocalAddress(), '/ibc-port/icacontroller-1'); + + const icaControllerPort2 = await when( + portAllocator.allocateICAControllerPort(), + ); + t.is(icaControllerPort2.getLocalAddress(), '/ibc-port/icacontroller-2'); + + const localPort = await when(portAllocator.allocateCustomLocalPort()); + t.is(localPort.getLocalAddress(), '/local/port-5'); + + const namedLocalPort = await when( + portAllocator.allocateCustomLocalPort('local-1'), + ); + t.is(namedLocalPort.getLocalAddress(), '/local/custom-local-1'); + + await t.throwsAsync(when(portAllocator.allocateCustomIBCPort('/test-1')), { + message: 'Invalid IBC port name: /test-1', + }); +}); + test('protocol connection listen', async t => { const zone = makeDurableZone(provideBaggage('network-protocol-connection')); const powers = prepareVowTools(zone); diff --git a/packages/orchestration/src/proposals/orchestration-proposal.js b/packages/orchestration/src/proposals/orchestration-proposal.js index 868372beab7..c889383bcda 100644 --- a/packages/orchestration/src/proposals/orchestration-proposal.js +++ b/packages/orchestration/src/proposals/orchestration-proposal.js @@ -1,9 +1,7 @@ // @ts-check import { V as E } from '@agoric/vat-data/vow.js'; -import { Far } from '@endo/far'; /** - * @import { AttenuatedNetwork } from '../types' * @import { OrchestrationService } from '../service.js' * @import { OrchestrationVat } from '../vat-orchestration.js' */ @@ -12,7 +10,7 @@ import { Far } from '@endo/far'; * @param {BootstrapPowers & { * consume: { * loadCriticalVat: VatLoader; - * networkVat: NetworkVat; + * portAllocator: PortAllocator; * }; * produce: { * orchestration: Producer; @@ -29,7 +27,7 @@ import { Far } from '@endo/far'; */ export const setupOrchestrationVat = async ( { - consume: { loadCriticalVat, networkVat }, + consume: { loadCriticalVat, portAllocator: portAllocatorP }, produce: { orchestrationVat, orchestration, @@ -49,17 +47,10 @@ export const setupOrchestrationVat = async ( orchestrationVat.reset(); orchestrationVat.resolve(vats.orchestration); - await networkVat; - /** @type {AttenuatedNetwork} */ - const network = Far('Attenuated Network', { - /** @param {string} localAddr */ - async bindPort(localAddr) { - return E(networkVat).bindPort(localAddr); - }, - }); + const portAllocator = await portAllocatorP; const newOrchestrationKit = await E(vats.orchestration).makeOrchestration({ - network, + portAllocator, }); orchestration.reset(); @@ -88,7 +79,7 @@ export const getManifestForOrchestration = (_powers, { orchestrationRef }) => ({ [setupOrchestrationVat.name]: { consume: { loadCriticalVat: true, - networkVat: true, + portAllocator: 'portAllocator', }, produce: { orchestration: 'orchestration', diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index e7532500445..ced55999021 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -11,10 +11,11 @@ import { makeICAConnectionAddress, parseAddress } from './utils/address.js'; import { makeTxPacket, parsePacketAck } from './utils/tx.js'; /** - * @import { AttenuatedNetwork, ChainAccount, ChainAddress } from './types.js'; + * @import { ChainAccount, ChainAddress } from './types.js'; * @import { IBCConnectionID } from '@agoric/vats'; * @import { Zone } from '@agoric/base-zone'; * @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; + * */ const { Fail, bare } = assert; @@ -24,7 +25,7 @@ const trace = makeTracer('Orchestration'); /** * @typedef {object} OrchestrationPowers - * @property {ERef} network + * @property {ERef} portAllocator */ /** @@ -232,17 +233,13 @@ const prepareOrchestration = (zone, createChainAccount) => powers.init(/** @type {keyof OrchestrationPowers} */ (name), power); } } - return { powers, icaControllerNonce: 0 }; + return { powers }; }, { self: { async bindPort() { - const network = getPower(this.state.powers, 'network'); - const port = await E(network).bindPort( - `/ibc-port/icacontroller-${this.state.icaControllerNonce}`, - ); - this.state.icaControllerNonce += 1; - return port; + const portAllocator = getPower(this.state.powers, 'portAllocator'); + return E(portAllocator).allocateICAControllerPort(); }, }, public: { diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index e41541f3957..bd3de46d26e 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -13,8 +13,6 @@ import type { UnbondingDelegation, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; -export type AttenuatedNetwork = Pick; - /** * static declaration of known chain types will allow type support for * additional chain-specific operations like `liquidStake` diff --git a/packages/pegasus/src/proposals/core-proposal.js b/packages/pegasus/src/proposals/core-proposal.js index e2f906d3c7c..dde255ccad1 100644 --- a/packages/pegasus/src/proposals/core-proposal.js +++ b/packages/pegasus/src/proposals/core-proposal.js @@ -19,7 +19,7 @@ export const getManifestForPegasus = ({ restoreRef }, { pegasusRef }) => ({ }, }, listenPegasus: { - consume: { networkVat: t, pegasusConnectionsAdmin: t, zoe: t }, + consume: { portAllocator: t, pegasusConnectionsAdmin: t, zoe: t }, produce: { pegasusConnections: t, pegasusConnectionsAdmin: t }, instance: { consume: { [CONTRACT_NAME]: t }, @@ -93,7 +93,7 @@ export const addPegasusTransferPort = async ( harden(addPegasusTransferPort); export const listenPegasus = async ({ - consume: { networkVat, pegasusConnectionsAdmin: pegasusNameAdmin, zoe }, + consume: { portAllocator, pegasusConnectionsAdmin: pegasusNameAdmin, zoe }, produce: { pegasusConnections, pegasusConnectionsAdmin }, instance: { consume: { [CONTRACT_NAME]: pegasusInstance }, @@ -104,7 +104,7 @@ export const listenPegasus = async ({ pegasusConnectionsAdmin.resolve(nameAdmin); const pegasus = await E(zoe).getPublicFacet(pegasusInstance); - const port = await E(networkVat).bindPort('/ibc-port/pegasus'); + const port = await E(portAllocator).allocateCustomIBCPort('pegasus'); return addPegasusTransferPort(port, pegasus, pegasusNameAdmin); }; harden(listenPegasus); diff --git a/packages/vats/src/proposals/network-proposal.js b/packages/vats/src/proposals/network-proposal.js index d9e8523b118..7b88b855ae4 100644 --- a/packages/vats/src/proposals/network-proposal.js +++ b/packages/vats/src/proposals/network-proposal.js @@ -12,7 +12,6 @@ import { makeScalarBigMapStore } from '@agoric/vat-data'; import { when } from '@agoric/vat-data/vow.js'; const NUM_IBC_PORTS_PER_CLIENT = 3; -const INTERCHAIN_ACCOUNT_CONTROLLER_PORT_PREFIX = 'icacontroller-'; /** * @param {SoloVats | NetVats} vats @@ -57,27 +56,24 @@ export const registerNetworkProtocols = async (vats, dibcBridgeManager) => { }; /** - * Create the network and IBC vats; produce `networkVat` in the core / bootstrap - * space. + * Create the network and IBC vats; produce `portAllocator` in the core / + * bootstrap space. * - * The `networkVat` is CLOSELY HELD in the core space, where later, we claim - * ports using `E(networkVat).bindPort(_path_)`. As discussed in - * `ProtocolHandler` docs, _path_ is: - * - * - /ibc-port/NAME for an IBC port with a known name or, - * - /ibc-port/ for an IBC port with a fresh name. + * The `portAllocator` is CLOSELY HELD in the core space, where later, we claim + * ports using `E(portAllocator).allocateCustomIBCPort`, for example. * * Contracts are expected to use the services of the network and IBC vats by way * of such ports. * * Testing facilities include: * - * - loopback ports: `E(networkVat).bindPort('/local/')` - * - an echo port: `E(vats.network).bindPort('/ibc-port/echo')` + * - loopback ports: `E(portAllocator).allocateCustomLocalPort()` + * - an echo port: `E(portAllocator).allocateCustomIBCPort("echo")` + * - echo port addrees: /ibc-port/custom-echo * * @param {BootstrapPowers & { * consume: { loadCriticalVat: VatLoader }; - * produce: { networkVat: Producer }; + * produce: { portAllocator: Producer }; * }} powers * @param {object} options * @param {{ networkRef: VatSourceRef; ibcRef: VatSourceRef }} options.options @@ -100,7 +96,7 @@ export const setupNetworkProtocols = async ( provisioning, vatUpgradeInfo: vatUpgradeInfoP, }, - produce: { networkVat, vatUpgradeInfo: produceVatUpgradeInfo }, + produce: { portAllocator, vatUpgradeInfo: produceVatUpgradeInfo }, }, options, ) => { @@ -121,27 +117,31 @@ export const setupNetworkProtocols = async ( info.init('ibc', ibcRef); info.init('network', networkRef); - networkVat.reset(); - networkVat.resolve(vats.network); + const portAllocatorP = E(vats.network).getPortAllocator(); + + portAllocator.reset(); + portAllocator.resolve(portAllocatorP); + + const allocator = await portAllocatorP; + const bridgeManager = await bridgeManagerP; const dibcBridgeManager = bridgeManager && E(bridgeManager).register(BRIDGE_ID.DIBC); // The Interchain Account (ICA) Controller must be bound to a port that starts // with 'icacontroller', so we provide one such port to each client. - let lastICAPort = 0; const makePorts = async () => { // Bind to some fresh ports (either unspecified name or `icacontroller-*`) // on the IBC implementation and provide them for the user to have. const ibcportP = []; for (let i = 0; i < NUM_IBC_PORTS_PER_CLIENT; i += 1) { - let bindAddr = '/ibc-port/'; if (i === NUM_IBC_PORTS_PER_CLIENT - 1) { - lastICAPort += 1; - bindAddr += `${INTERCHAIN_ACCOUNT_CONTROLLER_PORT_PREFIX}${lastICAPort}`; + const portP = when(E(allocator).allocateICAControllerPort()); + ibcportP.push(portP); + } else { + const portP = when(E(allocator).allocateCustomIBCPort()); + ibcportP.push(portP); } - const port = when(E(vats.network).bindPort(bindAddr)); - ibcportP.push(port); } return Promise.all(ibcportP); }; @@ -152,7 +152,7 @@ export const setupNetworkProtocols = async ( await registerNetworkProtocols(vats, dibcBridgeManager); // Add an echo listener on our ibc-port network (whether real or virtual). - const echoPort = await when(E(vats.network).bindPort('/ibc-port/echo')); + const echoPort = await when(E(allocator).allocateCustomIBCPort('echo')); const { listener } = await E(vats.network).makeEchoConnectionKit(); await when(E(echoPort).addListener(listener)); return E(client).assignBundle([_a => ({ ibcport: makePorts() })]); @@ -170,7 +170,7 @@ export const getManifestForNetwork = (_powers, { networkRef, ibcRef }) => ({ vatUpgradeInfo: true, }, produce: { - networkVat: 'network', + portAllocator: 'portAllocator', vatUpgradeInfo: true, }, zone: true, diff --git a/packages/vats/src/vat-network.js b/packages/vats/src/vat-network.js index 8c2c25ef076..98c6f9f1b98 100644 --- a/packages/vats/src/vat-network.js +++ b/packages/vats/src/vat-network.js @@ -3,6 +3,7 @@ import { makeDurableZone } from '@agoric/zone/durable.js'; import { prepareEchoConnectionKit, prepareLoopbackProtocolHandler, + preparePortAllocator, prepareRouterProtocol, } from '@agoric/network'; import { prepareVowTools } from '@agoric/vat-data/vow.js'; @@ -20,6 +21,11 @@ export function buildRootObject(_vatPowers, _args, baggage) { makeRouterProtocol(), ); + const makePortAllocator = preparePortAllocator(zone, powers); + const portAllocator = zone.makeOnce('PortAllocator', _key => + makePortAllocator({ protocol }), + ); + const makeLoopbackProtocolHandler = prepareLoopbackProtocolHandler( zone, powers, @@ -35,7 +41,8 @@ export function buildRootObject(_vatPowers, _args, baggage) { /** @param {Parameters} args */ unregisterProtocolHandler: (...args) => protocol.unregisterProtocolHandler(...args), - /** @param {Parameters} args */ - bindPort: (...args) => protocol.bindPort(...args), + getPortAllocator() { + return portAllocator; + }, }); } diff --git a/packages/vats/test/test-network.js b/packages/vats/test/test-network.js index 6bc41bffb17..0dde76387f8 100644 --- a/packages/vats/test/test-network.js +++ b/packages/vats/test/test-network.js @@ -125,10 +125,12 @@ test('network - ibc', async t => { bridgeHandler, ); + const portAllocator = await E(networkVat).getPortAllocator(); + // Actually test the ibc port binding. // TODO: Do more tests on the returned Port object. t.log('Opening a Listening Port'); - const p = await when(E(networkVat).bindPort('/ibc-port/')); + const p = await when(E(portAllocator).allocateCustomIBCPort()); const ev1 = await events.next(); t.assert(!ev1.done); t.deepEqual(ev1.value, ['bindPort', { packet: { source_port: 'port-1' } }]); diff --git a/packages/vats/tools/boot-test-utils.js b/packages/vats/tools/boot-test-utils.js index 28c7f12fc47..09afd1bae01 100644 --- a/packages/vats/tools/boot-test-utils.js +++ b/packages/vats/tools/boot-test-utils.js @@ -74,7 +74,6 @@ export const makeMock = log => network: Far('network', { registerProtocolHandler: noop, makeLoopbackProtocolHandler: noop, - bindPort: () => Far('network - listener', { addListener: noop }), }), }, });