Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getConnectionInfo in the view of the primary chain #9718

Merged
merged 5 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 102 additions & 102 deletions packages/boot/test/bootstrapTests/snapshots/orchestration.test.ts.md

Large diffs are not rendered by default.

Binary file not shown.
57 changes: 20 additions & 37 deletions packages/orchestration/src/chain-info.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { E } from '@endo/far';
import { mustMatch } from '@endo/patterns';
import { connectionKey } from './exos/chain-hub.js';
import { normalizeConnectionInfo } from './exos/chain-hub.js';
import fetchedChainInfo from './fetched-chain-info.js'; // Refresh with scripts/refresh-chain-info.ts
import { CosmosChainInfoShape } from './typeGuards.js';

Expand Down Expand Up @@ -65,46 +65,20 @@ const knownChains = /** @satisfies {Record<string, ChainInfo>} */ (

/** @typedef {typeof knownChains} KnownChains */

/**
* Utility to reverse connection info perspective.
*
* @param {IBCConnectionInfo} connInfo
* @returns {IBCConnectionInfo}
*/
const reverseConnInfo = connInfo => {
const { transferChannel } = connInfo;
return {
id: connInfo.counterparty.connection_id,
client_id: connInfo.counterparty.client_id,
counterparty: {
client_id: connInfo.client_id,
connection_id: connInfo.id,
prefix: {
key_prefix: '',
},
},
state: connInfo.state,
transferChannel: {
...transferChannel,
channelId: transferChannel.counterPartyChannelId,
counterPartyChannelId: transferChannel.channelId,
portId: transferChannel.counterPartyPortId,
counterPartyPortId: transferChannel.portId,
},
};
};

/**
* @param {ERef<import('@agoric/vats').NameHubKit['nameAdmin']>} agoricNamesAdmin
* @param {string} name
* @param {CosmosChainInfo} chainInfo
* @param {(...messages: string[]) => void} [log]
* @param {Set<string>} [handledConnections] connection keys that need not be
* updated
*/
export const registerChain = async (
agoricNamesAdmin,
name,
chainInfo,
log = () => {},
handledConnections = new Set(),
) => {
const { nameAdmin } = await E(agoricNamesAdmin).provideChild('chain');
const { nameAdmin: connAdmin } =
Expand All @@ -120,28 +94,37 @@ export const registerChain = async (
];

const { chainId } = chainInfo;
// FIXME updates redundantly, twice per edge
for (const [counterChainId, connInfo] of Object.entries(connections)) {
const key = connectionKey(chainId, counterChainId);
const normalizedConnInfo =
chainId < counterChainId ? connInfo : reverseConnInfo(connInfo);
const [key, connectionInfo] = normalizeConnectionInfo(
chainId,
counterChainId,
connInfo,
);
if (handledConnections.has(key)) {
continue;
}

promises.push(
E(connAdmin)
.update(key, normalizedConnInfo)
.update(key, connectionInfo)
.then(() => log(`registering agoricNames chainConnection.${key}`)),
);

handledConnections.add(key);
}
// Bundle to pipeline IO
await Promise.all(promises);
};

/**
* Register all the chains that are known statically.
*
* @param {ERef<import('@agoric/vats').NameHubKit['nameAdmin']>} agoricNamesAdmin
* @param {(...messages: string[]) => void} [log]
*/
export const registerChainNamespace = async (agoricNamesAdmin, log) => {
export const registerKnownChains = async (agoricNamesAdmin, log) => {
const handledConnections = new Set();
for await (const [name, info] of Object.entries(knownChains)) {
await registerChain(agoricNamesAdmin, name, info, log);
await registerChain(agoricNamesAdmin, name, info, log, handledConnections);
}
};
136 changes: 108 additions & 28 deletions packages/orchestration/src/exos/chain-hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,75 @@ export const connectionKey = (chainId1, chainId2) => {
return [chainId1, chainId2].sort().join(CHAIN_ID_SEPARATOR);
};

/**
* Utility to reverse connection info perspective.
*
* @param {IBCConnectionInfo} connInfo
* @returns {IBCConnectionInfo}
*/
export const reverseConnInfo = connInfo => {
const { transferChannel } = connInfo;
return {
id: connInfo.counterparty.connection_id,
client_id: connInfo.counterparty.client_id,
counterparty: {
client_id: connInfo.client_id,
connection_id: connInfo.id,
prefix: {
key_prefix: 'FIXME',
},
},
state: connInfo.state,
transferChannel: {
...transferChannel,
channelId: transferChannel.counterPartyChannelId,
counterPartyChannelId: transferChannel.channelId,
portId: transferChannel.counterPartyPortId,
counterPartyPortId: transferChannel.portId,
},
};
};

/**
* Convert the info to an undirected form.
*
* @param {string} primaryChainId
* @param {string} counterChainId
* @param {IBCConnectionInfo} directed
* @returns {[string, IBCConnectionInfo]}
*/
export const normalizeConnectionInfo = (
primaryChainId,
counterChainId,
directed,
) => {
const key = connectionKey(primaryChainId, counterChainId);
if (primaryChainId < counterChainId) {
return [key, directed];
} else {
return [key, reverseConnInfo(directed)];
}
};

/**
* Provide a view on the connection from the primary chain's perspective.
*
* @param {string} primaryChainId
* @param {string} counterChainId
* @param {IBCConnectionInfo} normalized
*/
const denormalizeConnectionInfo = (
primaryChainId,
counterChainId,
normalized,
) => {
if (primaryChainId < counterChainId) {
return normalized;
} else {
return reverseConnInfo(normalized);
}
};

const ChainIdArgShape = M.or(
M.string(),
M.splitRecord(
Expand Down Expand Up @@ -146,7 +215,8 @@ export const makeChainHub = (agoricNames, vowTools) => {
if (!connectionInfos.has(key)) {
connectionInfos.init(key, connectionInfo);
}
return connectionInfo;

return denormalizeConnectionInfo(chainId1, chainId2, connectionInfo);
} catch (e) {
console.error('lookupConnectionInfo', chainId1, chainId2, 'error', e);
throw makeError(`connection not found: ${chainId1}<->${chainId2}`);
Expand All @@ -161,26 +231,26 @@ export const makeChainHub = (agoricNames, vowTools) => {
/**
* @template {string} C1
* @template {string} C2
* @param {C1} chainName1
* @param {C2} chainName2
* @param {C1} primaryName
* @param {C2} counterName
* @returns {Promise<
* [ActualChainInfo<C1>, ActualChainInfo<C2>, IBCConnectionInfo]
* >}
*/
// eslint-disable-next-line no-restricted-syntax -- TODO more exact rules for vow best practices
async (chainName1, chainName2) => {
const [chain1, chain2] = await vowTools.asPromise(
async (primaryName, counterName) => {
const [primary, counter] = await vowTools.asPromise(
vowTools.allVows([
chainHub.getChainInfo(chainName1),
chainHub.getChainInfo(chainName2),
chainHub.getChainInfo(primaryName),
chainHub.getChainInfo(counterName),
]),
);
const connectionInfo = await vowTools.asPromise(
chainHub.getConnectionInfo(chain2, chain1),
chainHub.getConnectionInfo(primary, counter),
);
return /** @type {[ActualChainInfo<C1>, ActualChainInfo<C2>, IBCConnectionInfo]} */ ([
chain1,
chain2,
primary,
counter,
connectionInfo,
]);
},
Expand Down Expand Up @@ -218,43 +288,53 @@ export const makeChainHub = (agoricNames, vowTools) => {
return lookupChainInfo(chainName);
},
/**
* @param {string} chainId1
* @param {string} chainId2
* @param {IBCConnectionInfo} connectionInfo
* @param {string} primaryChainId
* @param {string} counterpartyChainId
* @param {IBCConnectionInfo} connectionInfo from primary to counterparty
*/
registerConnection(chainId1, chainId2, connectionInfo) {
const key = connectionKey(chainId1, chainId2);
connectionInfos.init(key, connectionInfo);
registerConnection(primaryChainId, counterpartyChainId, connectionInfo) {
const [key, normalized] = normalizeConnectionInfo(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I am a little confused by - is it necessary for the hotnewchain scenario, since we can't be certain they will give us noramlizedConnInfo? It would seem orchestration-proposal.js has everything normalized already at this point.

I wonder if it's better to throw if chainId1 > chainId2 so the user knows how they need to specify the perspective of connectionInfo. Alternatively, maybe a parameter name change would do the trick.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's better to throw if chainId1 > chainId2 so the user knows how they need to specify the perspective of connectionInfo.

That would not be reliable.

Alternatively, maybe a parameter name change would do the trick.

Agree, I'll improve the docs.

primaryChainId,
counterpartyChainId,
connectionInfo,
);
connectionInfos.init(key, normalized);
},

/**
* @param {string | { chainId: string }} chain1
* @param {string | { chainId: string }} chain2
* @param {string | { chainId: string }} primary the primary chain
* @param {string | { chainId: string }} counter the counterparty chain
* @returns {Vow<IBCConnectionInfo>}
*/
getConnectionInfo(chain1, chain2) {
const chainId1 = typeof chain1 === 'string' ? chain1 : chain1.chainId;
const chainId2 = typeof chain2 === 'string' ? chain2 : chain2.chainId;
const key = connectionKey(chainId1, chainId2);
getConnectionInfo(primary, counter) {
const primaryId = typeof primary === 'string' ? primary : primary.chainId;
const counterId = typeof counter === 'string' ? counter : counter.chainId;
const key = connectionKey(primaryId, counterId);
if (connectionInfos.has(key)) {
return vowTools.asVow(() => connectionInfos.get(key));
return vowTools.asVow(() =>
denormalizeConnectionInfo(
primaryId,
counterId,
connectionInfos.get(key),
),
);
}

return lookupConnectionInfo(chainId1, chainId2);
return lookupConnectionInfo(primaryId, counterId);
},

/**
* @template {string} C1
* @template {string} C2
* @param {C1} chainName1
* @param {C2} chainName2
* @param {C1} primaryName the primary chain name
* @param {C2} counterName the counterparty chain name
* @returns {Vow<
* [ActualChainInfo<C1>, ActualChainInfo<C2>, IBCConnectionInfo]
* >}
*/
getChainsAndConnection(chainName1, chainName2) {
getChainsAndConnection(primaryName, counterName) {
// @ts-expect-error XXX generic parameter propagation
return lookupChainsAndConnection(chainName1, chainName2);
return lookupChainsAndConnection(primaryName, counterName);
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Fail } from '@endo/errors';
import { E, Far } from '@endo/far';
import { makeMarshal } from '@endo/marshal';
import { makeTracer } from '@agoric/internal';
import { registerChainNamespace } from '../chain-info.js';
import { registerKnownChains } from '../chain-info.js';
import { CHAIN_KEY, CONNECTIONS_KEY } from '../exos/chain-hub.js';

const trace = makeTracer('CoreEvalOrchestration', true);
Expand Down Expand Up @@ -127,7 +127,7 @@ export const initChainInfo = async ({
await publishChainInfoToChainStorage(agoricNamesAdmin, chainStorageP);

// Now register the names
await registerChainNamespace(agoricNamesAdmin, trace);
await registerKnownChains(agoricNamesAdmin, trace);
};
harden(initChainInfo);

Expand Down
14 changes: 7 additions & 7 deletions packages/orchestration/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { M } from '@endo/patterns';

/**
* @import {TypedPattern} from '@agoric/internal';
* @import {CosmosChainInfo} from './cosmos-api.js';
* @import {ChainInfo, CosmosChainInfo} from './types.js';
*/

/**
Expand Down Expand Up @@ -81,9 +81,7 @@ export const IBCConnectionInfoShape = M.splitRecord({
transferChannel: IBCChannelInfoShape,
});

/**
* @type {TypedPattern<CosmosChainInfo>}
*/
/** @type {TypedPattern<CosmosChainInfo>} */
export const CosmosChainInfoShape = M.splitRecord(
{
chainId: M.string(),
Expand All @@ -96,11 +94,13 @@ export const CosmosChainInfoShape = M.splitRecord(
},
);

// FIXME more validation
export const ChainInfoShape = M.any();
/** @type {TypedPattern<ChainInfo>} */
export const ChainInfoShape = M.splitRecord({
chainId: M.string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe out of scope, but consider including a CosmosChainInfoShape for the second argument here. It seems we might be expecting a certain shape for IBCConnectionInfo.

icqEnabled: M.boolean() and stakingTokens: M.arrayOf({ denom: M.string() }) seem straight forward to incldue as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope and I think it won't work

});
export const LocalChainAccountShape = M.remotable('LocalChainAccount');
export const DenomShape = M.string();
// FIXME more validation
// TODO define for #9211
export const BrandInfoShape = M.any();

export const DenomAmountShape = { denom: DenomShape, value: M.bigint() };
Expand Down
4 changes: 2 additions & 2 deletions packages/orchestration/test/chain-info.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import test from 'ava';

import { makeNameHubKit } from '@agoric/vats';
import { registerChainNamespace } from '../src/chain-info.js';
import { registerKnownChains } from '../src/chain-info.js';

test('chain-info', async t => {
const { nameHub: agoricNames, nameAdmin: agoricNamesAdmin } =
makeNameHubKit();

await registerChainNamespace(agoricNamesAdmin);
await registerKnownChains(agoricNamesAdmin);
const chainNames = await agoricNames.lookup('chain');
t.like(await chainNames.lookup('cosmoshub'), {
chainId: 'cosmoshub-4',
Expand Down
Loading
Loading