Skip to content

Commit

Permalink
feat: chainHub retries
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Jul 9, 2024
1 parent 97a856b commit ec65bfa
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 66 deletions.
2 changes: 1 addition & 1 deletion packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const start = async (zcf, privateArgs, baggage) => {
zcf,
privateArgs.timerService,
vowTools,
makeChainHub(privateArgs.agoricNames),
makeChainHub(privateArgs.agoricNames, vowTools),
);

// ----------------
Expand Down
137 changes: 93 additions & 44 deletions packages/orchestration/src/exos/chain-hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@ import { E } from '@endo/far';
import { M } from '@endo/patterns';

import { VowShape } from '@agoric/vow';
// eslint-disable-next-line no-restricted-syntax
import { heapVowTools } from '@agoric/vow/vat.js';
import { makeHeapZone } from '@agoric/zone';
import { CosmosChainInfoShape, IBCConnectionInfoShape } from '../typeGuards.js';

// FIXME test thoroughly whether heap suffices for ChainHub
// eslint-disable-next-line no-restricted-syntax
const { allVows, watch } = heapVowTools;

/**
* @import {NameHub} from '@agoric/vats';
* @import {Vow} from '@agoric/vow';
* @import {Vow, VowTools} from '@agoric/vow';
* @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api.js';
* @import {ChainInfo, KnownChains} from '../chain-info.js';
* @import {Remote} from '@agoric/internal';
Expand Down Expand Up @@ -94,9 +88,10 @@ const ChainHubI = M.interface('ChainHub', {
* hub and repeat the registrations.
*
* @param {Remote<NameHub>} agoricNames
* @param {Zone} [zone]
* @param {VowTools} vowTools
*/
export const makeChainHub = (agoricNames, zone = makeHeapZone()) => {
export const makeChainHub = (agoricNames, vowTools) => {
const zone = makeHeapZone();
/** @type {MapStore<string, CosmosChainInfo>} */
const chainInfos = zone.mapStore('chainInfos', {
keyShape: M.string(),
Expand All @@ -108,6 +103,89 @@ export const makeChainHub = (agoricNames, zone = makeHeapZone()) => {
valueShape: IBCConnectionInfoShape,
});

const lookupChainInfo = vowTools.retriable(
zone,
'lookupChainInfo',
/** @param {string} chainName */
// eslint-disable-next-line no-restricted-syntax -- TODO more exact rules for vow best practices
async chainName => {
await null;
try {
const chainInfo = await E(agoricNames).lookup(CHAIN_KEY, chainName);
// It may have been set by another concurrent call
// TODO consider makeAtomicProvider for vows
if (!chainInfos.has(chainName)) {
chainInfos.init(chainName, chainInfo);
}
return chainInfo;
} catch (e) {
console.error('lookupChainInfo', chainName, 'error', e);
throw makeError(`chain not found:${chainName}`);
}
},
);

const lookupConnectionInfo = vowTools.retriable(
zone,
'lookupConnectionInfo',
/**
* @param {string} chainId1
* @param {string} chainId2
*/
// eslint-disable-next-line no-restricted-syntax -- TODO more exact rules for vow best practices
async (chainId1, chainId2) => {
await null;
const key = connectionKey(chainId1, chainId2);
try {
const connectionInfo = await E(agoricNames).lookup(
CONNECTIONS_KEY,
key,
);
// It may have been set by another concurrent call
// TODO consider makeAtomicProvider for vows
if (!connectionInfos.has(key)) {
connectionInfos.init(key, connectionInfo);
}
return connectionInfo;
} catch (e) {
console.error('lookupConnectionInfo', chainId1, chainId2, 'error', e);
throw makeError(`connection not found: ${chainId1}<->${chainId2}`);
}
},
);

/* eslint-disable no-use-before-define -- chainHub defined below */
const lookupChainsAndConnection = vowTools.retriable(
zone,
'lookupChainsAndConnection',
/**
* @template {string} C1
* @template {string} C2
* @param {C1} chainName1
* @param {C2} chainName2
* @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(
vowTools.allVows([
chainHub.getChainInfo(chainName1),
chainHub.getChainInfo(chainName2),
]),
);
const connectionInfo = await vowTools.asPromise(
chainHub.getConnectionInfo(chain2, chain1),
);
return /** @type {[ActualChainInfo<C1>, ActualChainInfo<C2>, IBCConnectionInfo]} */ ([
chain1,
chain2,
connectionInfo,
]);
},
);

const chainHub = zone.exo('ChainHub', ChainHubI, {
/**
* Register a new chain. The name will override a name in well known chain
Expand All @@ -133,19 +211,11 @@ export const makeChainHub = (agoricNames, zone = makeHeapZone()) => {
// Either from registerChain or memoized remote lookup()
if (chainInfos.has(chainName)) {
return /** @type {Vow<ActualChainInfo<K>>} */ (
watch(chainInfos.get(chainName))
vowTools.asVow(() => chainInfos.get(chainName))
);
}

return watch(E(agoricNames).lookup(CHAIN_KEY, chainName), {
onFulfilled: chainInfo => {
chainInfos.init(chainName, chainInfo);
return chainInfo;
},
onRejected: _cause => {
throw makeError(`chain not found:${chainName}`);
},
});
return lookupChainInfo(chainName);
},
/**
* @param {string} chainId1
Expand All @@ -167,18 +237,10 @@ export const makeChainHub = (agoricNames, zone = makeHeapZone()) => {
const chainId2 = typeof chain2 === 'string' ? chain2 : chain2.chainId;
const key = connectionKey(chainId1, chainId2);
if (connectionInfos.has(key)) {
return watch(connectionInfos.get(key));
return vowTools.asVow(() => connectionInfos.get(key));
}

return watch(E(agoricNames).lookup(CONNECTIONS_KEY, key), {
onFulfilled: connectionInfo => {
connectionInfos.init(key, connectionInfo);
return connectionInfo;
},
onRejected: _cause => {
throw makeError(`connection not found: ${chainId1}<->${chainId2}`);
},
});
return lookupConnectionInfo(chainId1, chainId2);
},

/**
Expand All @@ -191,21 +253,8 @@ export const makeChainHub = (agoricNames, zone = makeHeapZone()) => {
* >}
*/
getChainsAndConnection(chainName1, chainName2) {
return watch(
allVows([
chainHub.getChainInfo(chainName1),
chainHub.getChainInfo(chainName2),
]),
{
onFulfilled: ([chain1, chain2]) => {
return watch(chainHub.getConnectionInfo(chain2, chain1), {
onFulfilled: connectionInfo => {
return [chain1, chain2, connectionInfo];
},
});
},
},
);
// @ts-expect-error XXX generic parameter propagation
return lookupChainsAndConnection(chainName1, chainName2);
},
});

Expand Down
9 changes: 6 additions & 3 deletions packages/orchestration/src/proposals/start-stakeAtom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { makeTracer } from '@agoric/internal';
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js';
import { heapVowE as E } from '@agoric/vow/vat.js';
import { prepareVowTools } from '@agoric/vow';
import { makeHeapZone } from '@agoric/zone';
import { E } from '@endo/far';
import { makeChainHub } from '../exos/chain-hub.js';

/**
Expand Down Expand Up @@ -44,9 +46,10 @@ export const startStakeAtom = async ({
const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH);
const marshaller = await E(board).getPublishingMarshaller();

const chainHub = makeChainHub(await agoricNames);
const vt = prepareVowTools(makeHeapZone());
const chainHub = makeChainHub(await agoricNames, vt);

const [_, cosmoshub, connectionInfo] = await E.when(
const [_, cosmoshub, connectionInfo] = await vt.when(
chainHub.getChainsAndConnection('agoric', 'cosmoshub'),
);

Expand Down
9 changes: 6 additions & 3 deletions packages/orchestration/src/proposals/start-stakeOsmo.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { makeTracer } from '@agoric/internal';
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js';
import { heapVowE as E } from '@agoric/vow/vat.js';
import { prepareVowTools } from '@agoric/vow';
import { makeHeapZone } from '@agoric/zone';
import { E } from '@endo/far';
import { makeChainHub } from '../exos/chain-hub.js';

/**
Expand Down Expand Up @@ -45,9 +47,10 @@ export const startStakeOsmo = async ({
const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH);
const marshaller = await E(board).getPublishingMarshaller();

const chainHub = makeChainHub(await agoricNames);
const vt = prepareVowTools(makeHeapZone());
const chainHub = makeChainHub(await agoricNames, vt);

const [_, osmosis, connectionInfo] = await E.when(
const [_, osmosis, connectionInfo] = await vt.when(
chainHub.getChainsAndConnection('agoric', 'osmosis'),
);

Expand Down
4 changes: 2 additions & 2 deletions packages/orchestration/src/utils/start-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export const provideOrchestration = (
const zone = makeDurableZone(baggage);
const { agoricNames, timerService } = remotePowers;

const chainHub = makeChainHub(agoricNames);

const vowTools = prepareVowTools(zone.subZone('vows'));

const chainHub = makeChainHub(agoricNames, vowTools);

const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);
const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit(
zone,
Expand Down
38 changes: 34 additions & 4 deletions packages/orchestration/test/exos/chain-hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import test from '@endo/ses-ava/prepare-endo.js';

import { makeNameHubKit } from '@agoric/vats';
import { prepareSwingsetVowTools } from '@agoric/vow/vat.js';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import { makeChainHub } from '../../src/exos/chain-hub.js';
import { provideDurableZone } from '../supports.js';
import { registerChainNamespace } from '../../src/chain-info.js';

const connection = {
id: 'connection-1',
Expand All @@ -29,15 +31,43 @@ const connection = {
},
} as const;

test('getConnectionInfo', async t => {
// fresh state for each test
const setup = () => {
const zone = provideDurableZone('root');
const vt = prepareSwingsetVowTools(zone);
const { nameHub } = makeNameHubKit();
const chainHub = makeChainHub(nameHub, zone);
const { nameHub, nameAdmin } = makeNameHubKit();
const chainHub = makeChainHub(nameHub, vt);

return { chainHub, nameAdmin, vt };
};

test.serial('getChainInfo', async t => {
const { chainHub, nameAdmin, vt } = setup();
// use fetched chain info
await registerChainNamespace(nameAdmin);

const vow = chainHub.getChainInfo('celestia');
t.like(await vt.asPromise(vow), { chainId: 'celestia' });
});

test.serial('concurrency', async t => {
const { chainHub, nameAdmin, vt } = setup();
// use fetched chain info
await registerChainNamespace(nameAdmin);

const v1 = chainHub.getChainInfo('celestia');
const v2 = chainHub.getChainInfo('celestia');
t.like(await vt.asPromise(vt.allVows([v1, v2])), [
{ chainId: 'celestia' },
{ chainId: 'celestia' },
]);
});

test.serial('getConnectionInfo', async t => {
const { chainHub, vt } = setup();

const aChain = { chainId: 'a-1' };
const bChain = { chainId: 'b-2' };

chainHub.registerConnection(aChain.chainId, bChain.chainId, connection);

// Look up by string or info object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test('deposit, withdraw', async t => {
Far('MockZCF', {}),
timer,
vowTools,
makeChainHub(bootstrap.agoricNames),
makeChainHub(bootstrap.agoricNames, vowTools),
);

t.log('request account from vat-localchain');
Expand Down Expand Up @@ -107,7 +107,7 @@ test('delegate, undelegate', async t => {
Far('MockZCF', {}),
timer,
vowTools,
makeChainHub(bootstrap.agoricNames),
makeChainHub(bootstrap.agoricNames, vowTools),
);

t.log('request account from vat-localchain');
Expand Down Expand Up @@ -170,7 +170,7 @@ test('transfer', async t => {
Far('MockZCF', {}),
timer,
vowTools,
makeChainHub(bootstrap.agoricNames),
makeChainHub(bootstrap.agoricNames, vowTools),
);

t.log('request account from vat-localchain');
Expand Down
7 changes: 3 additions & 4 deletions packages/orchestration/test/facade.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';

import { heapVowE as E } from '@agoric/vow/vat.js';
import { prepareSwingsetVowTools } from '@agoric/vow/vat.js';
import { setupZCFTest } from '@agoric/zoe/test/unitTests/zcf/setupZcfTest.js';
import type { CosmosChainInfo, IBCConnectionInfo } from '../src/cosmos-api.js';
import type { Chain } from '../src/orchestration-api.js';
Expand Down Expand Up @@ -39,8 +39,6 @@ export const mockChainConnection: IBCConnectionInfo = {
},
};

const makeLocalOrchestrationAccountKit = () => assert.fail(`not used`);

test('chain info', async t => {
const { bootstrap, facadeServices, commonPrivateArgs } = await commonSetup(t);

Expand All @@ -49,6 +47,7 @@ test('chain info', async t => {
// After setupZCFTest because this disables relaxDurabilityRules
// which breaks Zoe test setup's fakeVatAdmin
const zone = provideDurableZone('test');
const vt = prepareSwingsetVowTools(zone);

const orchKit = provideOrchestration(
zcf,
Expand Down Expand Up @@ -77,7 +76,7 @@ test('chain info', async t => {
});

const result = (await handle()) as Chain<any>;
t.deepEqual(await E.when(result.getChainInfo()), mockChainInfo);
t.deepEqual(await vt.when(result.getChainInfo()), mockChainInfo);
});

test.todo('contract upgrade');
6 changes: 4 additions & 2 deletions packages/vow/src/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { makeWhen } from './when.js';

/**
* @import {Zone} from '@agoric/base-zone';
* @import {IsRetryableReason, AsPromiseFunction, EVow} from './types.js';
* @import {IsRetryableReason, AsPromiseFunction, EVow, Vow, ERef} from './types.js';
*/

/**
Expand Down Expand Up @@ -40,9 +40,11 @@ export const prepareVowTools = (zone, powers = {}) => {
*
* The internal functions
*
* @template T
* @param {Zone} fnZone - the zone for the named function
* @param {string} name
* @param {(...args: unknown[]) => unknown} fn
* @param {(...args: unknown[]) => ERef<T>} fn
* @returns {(...args: unknown[]) => Vow<T>}
*/
const retriable =
(fnZone, name, fn) =>
Expand Down

0 comments on commit ec65bfa

Please sign in to comment.