Skip to content

Commit d418451

Browse files
committed
docs: add code examples
1 parent 425e2ed commit d418451

File tree

2 files changed

+296
-4
lines changed

2 files changed

+296
-4
lines changed

main/guides/orchestration/abc.js

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import { Fail, makeError } from '@endo/errors';
2+
import { E } from '@endo/far';
3+
import { M } from '@endo/patterns';
4+
5+
import { VowShape } from '@agoric/vow';
6+
// eslint-disable-next-line no-restricted-syntax
7+
import { heapVowTools } from '@agoric/vow/vat.js';
8+
import { makeHeapZone } from '@agoric/zone';
9+
import { CosmosChainInfoShape, IBCConnectionInfoShape } from '../typeGuards.js';
10+
11+
// FIXME test thoroughly whether heap suffices for ChainHub
12+
// eslint-disable-next-line no-restricted-syntax
13+
const { allVows, watch } = heapVowTools;
14+
15+
/**
16+
* @import {NameHub} from '@agoric/vats';
17+
* @import {Vow} from '@agoric/vow';
18+
* @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api.js';
19+
* @import {ChainInfo, KnownChains} from '../chain-info.js';
20+
* @import {Remote} from '@agoric/internal';
21+
* @import {Zone} from '@agoric/zone';
22+
*/
23+
24+
/**
25+
* @template {string} K
26+
* @typedef {K extends keyof KnownChains
27+
* ? Omit<KnownChains[K], 'connections'>
28+
* : ChainInfo} ActualChainInfo
29+
*/
30+
31+
/** agoricNames key for ChainInfo hub */
32+
export const CHAIN_KEY = 'chain';
33+
/** namehub for connection info */
34+
export const CONNECTIONS_KEY = 'chainConnection';
35+
36+
/**
37+
* Character used in a connection tuple key to separate the two chain ids. Valid
38+
* because a chainId can contain only alphanumerics and dash.
39+
*
40+
* Vstorage keys can be only alphanumerics, dash or underscore. That leaves
41+
* underscore as the only valid separator.
42+
*
43+
* @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md}
44+
*/
45+
const CHAIN_ID_SEPARATOR = '_';
46+
47+
/**
48+
* The entries of the top-level namehubs in agoricNames are reflected to
49+
* vstorage. But only the top level. So we combine the 2 chain ids into 1 key.
50+
* Connections are directionless, so we sort the ids.
51+
*
52+
* @param {string} chainId1
53+
* @param {string} chainId2
54+
*/
55+
export const connectionKey = (chainId1, chainId2) => {
56+
if (
57+
chainId1.includes(CHAIN_ID_SEPARATOR) ||
58+
chainId2.includes(CHAIN_ID_SEPARATOR)
59+
) {
60+
Fail`invalid chain id ${chainId1} or ${chainId2}`;
61+
}
62+
return [chainId1, chainId2].sort().join(CHAIN_ID_SEPARATOR);
63+
};
64+
65+
const ChainIdArgShape = M.or(
66+
M.string(),
67+
M.splitRecord(
68+
{
69+
chainId: M.string(),
70+
},
71+
undefined,
72+
M.any(),
73+
),
74+
);
75+
76+
const ChainHubI = M.interface('ChainHub', {
77+
registerChain: M.call(M.string(), CosmosChainInfoShape).returns(),
78+
getChainInfo: M.call(M.string()).returns(VowShape),
79+
registerConnection: M.call(
80+
M.string(),
81+
M.string(),
82+
IBCConnectionInfoShape,
83+
).returns(),
84+
getConnectionInfo: M.call(ChainIdArgShape, ChainIdArgShape).returns(VowShape),
85+
getChainsAndConnection: M.call(M.string(), M.string()).returns(VowShape),
86+
});
87+
88+
/**
89+
* Make a new ChainHub in the zone (or in the heap if no zone is provided).
90+
*
91+
* The resulting object is an Exo singleton. It has no precious state. It's only
92+
* state is a cache of queries to agoricNames and whatever info was provided in
93+
* registration calls. When you need a newer version you can simply make a hub
94+
* hub and repeat the registrations.
95+
*
96+
* @param {Remote<NameHub>} agoricNames
97+
* @param {Zone} [zone]
98+
*/
99+
export const makeChainHub = (agoricNames, zone = makeHeapZone()) => {
100+
/** @type {MapStore<string, CosmosChainInfo>} */
101+
const chainInfos = zone.mapStore('chainInfos', {
102+
keyShape: M.string(),
103+
valueShape: CosmosChainInfoShape,
104+
});
105+
/** @type {MapStore<string, IBCConnectionInfo>} */
106+
const connectionInfos = zone.mapStore('connectionInfos', {
107+
keyShape: M.string(),
108+
valueShape: IBCConnectionInfoShape,
109+
});
110+
111+
const chainHub = zone.exo('ChainHub', ChainHubI, {
112+
/**
113+
* Register a new chain. The name will override a name in well known chain
114+
* names.
115+
*
116+
* If a durable zone was not provided, registration will not survive a
117+
* reincarnation of the vat. Then if the chain is not yet in the well known
118+
* names at that point, it will have to be registered again. In an unchanged
119+
* contract `start` the call will happen again naturally.
120+
*
121+
* @param {string} name
122+
* @param {CosmosChainInfo} chainInfo
123+
*/
124+
registerChain(name, chainInfo) {
125+
chainInfos.init(name, chainInfo);
126+
},
127+
/**
128+
* @template {string} K
129+
* @param {K} chainName
130+
* @returns {Vow<ActualChainInfo<K>>}
131+
*/
132+
getChainInfo(chainName) {
133+
// Either from registerChain or memoized remote lookup()
134+
if (chainInfos.has(chainName)) {
135+
return /** @type {Vow<ActualChainInfo<K>>} */ (
136+
watch(chainInfos.get(chainName))
137+
);
138+
}
139+
140+
return watch(E(agoricNames).lookup(CHAIN_KEY, chainName), {
141+
onFulfilled: chainInfo => {
142+
chainInfos.init(chainName, chainInfo);
143+
return chainInfo;
144+
},
145+
onRejected: _cause => {
146+
throw makeError(`chain not found:${chainName}`);
147+
},
148+
});
149+
},
150+
/**
151+
* @param {string} chainId1
152+
* @param {string} chainId2
153+
* @param {IBCConnectionInfo} connectionInfo
154+
*/
155+
registerConnection(chainId1, chainId2, connectionInfo) {
156+
const key = connectionKey(chainId1, chainId2);
157+
connectionInfos.init(key, connectionInfo);
158+
},
159+
160+
/**
161+
* @param {string | { chainId: string }} chain1
162+
* @param {string | { chainId: string }} chain2
163+
* @returns {Vow<IBCConnectionInfo>}
164+
*/
165+
cgetConnectionInfo(chain1, chain2) {
166+
const chainId1 = typeof chain1 === 'string' ? chain1 : chain1.chainId;
167+
const chainId2 = typeof chain2 === 'string' ? chain2 : chain2.chainId;
168+
const key = connectionKey(chainId1, chainId2);
169+
if (connectionInfos.has(key)) {
170+
return watch(connectionInfos.get(key));
171+
}
172+
173+
return watch(E(agoricNames).lookup(CONNECTIONS_KEY, key), {
174+
onFulfilled: connectionInfo => {
175+
connectionInfos.init(key, connectionInfo);
176+
return connectionInfo;
177+
},
178+
onRejected: _cause => {
179+
throw makeError(`connection not found: ${chainId1}<->${chainId2}`);
180+
},
181+
});
182+
},
183+
184+
/**
185+
* @template {string} C1
186+
* @template {string} C2
187+
* @param {C1} chainName1
188+
* @param {C2} chainName2
189+
* @returns {Vow<
190+
* [ActualChainInfo<C1>, ActualChainInfo<C2>, IBCConnectionInfo]
191+
* >}
192+
*/
193+
getChainsAndConnection(chainName1, chainName2) {
194+
return watch(
195+
allVows([
196+
chainHub.getChainInfo(chainName1),
197+
chainHub.getChainInfo(chainName2),
198+
]),
199+
{
200+
onFulfilled: ([chain1, chain2]) => {
201+
return watch(chainHub.getConnectionInfo(chain2, chain1), {
202+
onFulfilled: connectionInfo => {
203+
return [chain1, chain2, connectionInfo];
204+
},
205+
});
206+
},
207+
},
208+
);
209+
},
210+
});
211+
212+
return chainHub;
213+
};
214+
/** @typedef {ReturnType<typeof makeChainHub>} ChainHub */

main/guides/orchestration/chainhub.md

+82-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,100 @@
1-
# Chain Hub
1+
# ChainHub
22

3-
This [code module](https://github.com/Agoric/agoric-sdk/blob/859d8c0d151ff6f686583db1eaf72efb89cc7648/packages/orchestration/src/exos/chain-hub.js#L99) is designed for managing chain and IBC connection information. It facilitate the registration and retrieval of chain and connection data. Let's break down the components and functionalities of this code.
3+
The [ChainHub API](https://github.com/Agoric/agoric-sdk/blob/859d8c0d151ff6f686583db1eaf72efb89cc7648/packages/orchestration/src/exos/chain-hub.js#L99) is responsible managing chain and IBC connection information. It facilitate the registration and retrieval of chain and connection data.
44

5-
The `makeChainHub` function accepts a NameHub reference (`agoricNames`) and an optional Zone for managing data durability. Inside the function two `MapStores` are created:
5+
```js
6+
const zone = makeDurableZone(baggage);
7+
const { agoricNames } = remotePowers;
8+
const chainHub = makeChainHub(agoricNames, zone);
9+
```
10+
11+
The `makeChainHub` function accepts a `Remote<NameHub>` reference (`agoricNames`) and an optional `Zone` for managing data durability. The `makeChainHub` fuction creates a new `ChainHub` either in the specified zone or in the heap if no zone is provided. The resulting object is an Exo singleton, which means it has no previous state. Its state consists only of a cache of queries to `agoricNames` and the information provided in registration calls.
12+
13+
The `ChainHub` objects maintains two `MapStores`:
614

715
- `chainInfos`: for storing `CosmosChainInfo` objects.
816
- `connectionInfos`: for storing `IBCConnectionInfo` objects.
917

18+
These `MapStores` are not exposed directly. They are abstracted and used internally by the methods provided by the ChainHub.
19+
1020
# ChainHub Interface
1121

1222
The core functionality is encapsulated within the `makeChainHub` function, which sets up a new ChainHub in the given zone. The ChainHub is responsible for:
1323

14-
- **Registering Chain Information (`registerChain`)**: Stores information about a blockchain inside the `chainInfos` mapstore, which can be used for quickly looking up details without querying a remote source.
24+
- **Registering Chain Information (`registerChain`)**: Stores information about a chain inside the `chainInfos` mapstore, which can be used for quickly looking up details without querying a remote source.
25+
26+
```js
27+
const chainInfo = harden({
28+
chainId: 'agoric-3',
29+
icaEnabled: false,
30+
icqEnabled: false,
31+
pfmEnabled: false,
32+
ibcHooksEnabled: false,
33+
stakingTokens: [{ denom: 'uist' }],
34+
});
35+
let nonce = 0n;
36+
37+
const chainKey = `${chainInfo.chainId}-${(nonce += 1n)}`;
38+
39+
chainHub.registerChain(chainKey, chainInfo);
40+
```
41+
42+
The function takes two parameters: `name`, which is a `string` representing the unique identifier of the chain, and `chainInfo`, which is an object structured according to the `CosmosChainInfo` format.
1543

1644
- **Retrieving Chain Information (`getChainInfo`)**: Retrieves stored chain information from the `chainInfos` mapstore or fetches it from a remote source if not available locally.
1745

46+
```js
47+
chainHub.getChainInfo('agoric-3');
48+
```
49+
50+
The function takes a single parameter, `chainName`, which is a `string` template type `K`, and returns a promise (`Vow`) that resolves to `ActualChainInfo<K>`, providing detailed information about the specified chain based on its name.
51+
1852
- **Registering Connection Information (`registerConnection`)**: Stores information about a connection between two chains in `connectionInfos` mapstore, such as IBC connection details.
1953

54+
```js
55+
const chainConnection = {
56+
id: 'connection-0',
57+
client_id: '07-tendermint-2',
58+
counterparty: {
59+
client_id: '07-tendermint-2',
60+
connection_id: 'connection-1',
61+
prefix: {
62+
key_prefix: '',
63+
},
64+
},
65+
state: 3 /* IBCConnectionState.STATE_OPEN */,
66+
transferChannel: {
67+
portId: 'transfer',
68+
channelId: 'channel-1',
69+
counterPartyChannelId: 'channel-1',
70+
counterPartyPortId: 'transfer',
71+
ordering: 1 /* Order.ORDER_UNORDERED */,
72+
state: 3 /* IBCConnectionState.STATE_OPEN */,
73+
version: 'ics20-1',
74+
},
75+
};
76+
77+
chainHub.registerConnection('agoric-3', 'cosmoshub', chainConnection);
78+
```
79+
80+
The function accepts three parameters: `chainId1` and `chainId2`, both of which are `strings` representing the identifiers of the two chains being connected, and `connectionInfo`, which is an object containing the details of the IBC connection as specified by the `IBCConnectionInfo` format
81+
2082
- **Retrieving Connection Information (`getConnectionInfo`)**: Retrieves stored connection information from `connectionInfos` mapstore or fetches it from a remote source if not available locally.
2183

84+
```js
85+
const chainConnection = await E.when(
86+
chainHub.getConnectionInfo('agoric-3', 'cosmoshub'),
87+
);
88+
```
89+
90+
The function takes two parameters, `chain1` and `chain2`, each of which can be either a `string` representing a chain identifier or an `object` with a `chainId` property, and it returns a promise (`Vow`) that resolves with an `IBCConnectionInfo` object detailing the connection between the two chains.
91+
2292
- **Retrieving Combined Chain and Connection Information (`getChainsAndConnection`)**: A composite function that fetches information about two chains and their connection simultaneously.
93+
94+
```js
95+
const [agoric3, cosmoshub, connectionInfo] = await E.when(
96+
chainHub.getChainsAndConnection('agoric-3', 'cosmoshub'),
97+
);
98+
```
99+
100+
The function accepts two parameters, `chainName1` and `chainName2`, both of which are strings but defined as template types `C1` and `C2` respectively. It returns a promise (`Vow`) that resolves to a tuple containing the detailed information of both chains, `ActualChainInfo<C1>` and `ActualChainInfo<C2>`, along with their IBC connection information (`IBCConnectionInfo`).

0 commit comments

Comments
 (0)