Skip to content

Commit

Permalink
feat: publish local and remote ibc addresses to vstorage
Browse files Browse the repository at this point in the history
- publishes remoteAddress (RemoteIBCAddress) and localAddress (LocalIBCAddress) to vstorage for CosmosOrchestrationAccount
- goal is to faciliate off-chain clients, which need portId, connectionId, and channelId for the host and controller to perform queries
- a better design might publish these values individually, versus putting the burden of parsing on the client
- refs: #9066
  • Loading branch information
0xpatrickdev committed Aug 8, 2024
1 parent 9f991a3 commit c227ce2
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 91 deletions.
21 changes: 18 additions & 3 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ test.serial('stakeAtom - smart wallet', async t => {
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-account', numWantsSatisfied: 1 },
});
t.is(readLatest('published.stakeAtom.accounts.cosmos1test'), '');
t.deepEqual(readLatest('published.stakeAtom.accounts.cosmos1test'), {
localAddress:
'/ibc-port/icacontroller-1/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-1',
remoteAddress:
'/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-1',
});

const { ATOM } = agoricNamesRemotes.brand;
ATOM || Fail`ATOM missing from agoricNames`;
Expand Down Expand Up @@ -297,7 +302,12 @@ test('basic-flows', async t => {
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-coa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.basicFlows.cosmos1test'), '');
t.deepEqual(readLatest('published.basicFlows.cosmos1test'), {
localAddress:
'/ibc-port/icacontroller-4/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4',
remoteAddress:
'/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4',
});

// create a local orchestration account
await wd.executeOffer({
Expand Down Expand Up @@ -389,7 +399,12 @@ test.serial('basic-flows - portfolio holder', async t => {
status: { id: 'request-portfolio-acct', numWantsSatisfied: 1 },
});
// XXX this overrides a previous account, since mocks only provide one address
t.is(readLatest('published.basicFlows.cosmos1test'), '');
t.deepEqual(readLatest('published.basicFlows.cosmos1test'), {
localAddress:
'/ibc-port/icacontroller-3/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-3',
remoteAddress:
'/ibc-hop/connection-1/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-3',
});
// XXX this overrides a previous account, since mocks only provide one address
t.is(readLatest('published.basicFlows.agoric1mockVlocalchainAddress'), '');

Expand Down
7 changes: 6 additions & 1 deletion packages/boot/test/orchestration/restart-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,12 @@ test.serial('stakeAtom', async t => {
const accountPath = 'published.stakeAtom.accounts.cosmos1test';
t.throws(() => readLatest(accountPath));
t.is(await flushInboundQueue(), 1);
t.is(readLatest(accountPath), '');
t.deepEqual(readLatest(accountPath), {
localAddress:
'/ibc-port/icacontroller-1/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-1',
remoteAddress:
'/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-1',
});
// request-account is complete

const { ATOM } = agoricNamesRemotes.brand;
Expand Down
25 changes: 16 additions & 9 deletions packages/orchestration/src/examples/stakeIca.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,24 @@ export const start = async (zcf, privateArgs, baggage) => {
? await E(orchestration).provideICQConnection(controllerConnectionId)
: undefined;

const accountAddress = await E(account).getAddress();
trace('account address', accountAddress);
const [chainAddress, localAddress, remoteAddress] = await Promise.all([
E(account).getAddress(),
E(account).getLocalAddress(),
E(account).getRemoteAddress(),
]);
trace('account address', chainAddress);
const accountNode = await E(accountsStorageNode).makeChildNode(
accountAddress.value,
chainAddress.value,
);
const holder = makeCosmosOrchestrationAccount(
{ chainAddress, bondDenom, localAddress, remoteAddress },
{
account,
storageNode: accountNode,
icqConnection,
timer,
},
);
const holder = makeCosmosOrchestrationAccount(accountAddress, bondDenom, {
account,
storageNode: accountNode,
icqConnection,
timer,
});
return holder;
}

Expand Down
38 changes: 33 additions & 5 deletions packages/orchestration/src/exos/cosmos-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js';
* @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js';
* @import {JsonSafe} from '@agoric/cosmic-proto';
* @import {Matcher} from '@endo/patterns';
* @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js';
*/

const trace = makeTracer('ComosOrchestrationAccountHolder');
Expand All @@ -61,12 +62,21 @@ const { Vow$ } = NetworkShape; // TODO #9611
* topicKit: RecorderKit<ComosOrchestrationAccountNotification>;
* account: IcaAccount;
* chainAddress: ChainAddress;
* localAddress: LocalIbcAddress;
* remoteAddress: RemoteIbcAddress;
* icqConnection: ICQConnection | undefined;
* bondDenom: string;
* timer: Remote<TimerService>;
* }} State
*/

/**
* @typedef {{
* localAddress: LocalIbcAddress;
* remoteAddress: RemoteIbcAddress;
* }} CosmosOrchestrationAccountStorageState
*/

/** @see {OrchestrationAccountI} */
export const IcaAccountHolderI = M.interface('IcaAccountHolder', {
...orchestrationAccountMethods,
Expand Down Expand Up @@ -148,23 +158,41 @@ export const prepareCosmosOrchestrationAccountKit = (
}),
},
/**
* @param {ChainAddress} chainAddress
* @param {string} bondDenom e.g. 'uatom'
* @param {object} info
* @param {ChainAddress} info.chainAddress
* @param {string} info.bondDenom e.g. 'uatom'
* @param {LocalIbcAddress} info.localAddress
* @param {RemoteIbcAddress} info.remoteAddress
* @param {object} io
* @param {IcaAccount} io.account
* @param {Remote<StorageNode>} io.storageNode
* @param {ICQConnection | undefined} io.icqConnection
* @param {Remote<TimerService>} io.timer
* @returns {State}
*/
(chainAddress, bondDenom, io) => {
({ chainAddress, bondDenom, localAddress, remoteAddress }, io) => {
const { storageNode, ...rest } = io;
// must be the fully synchronous maker because the kit is held in durable state
const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]);
// TODO determine what goes in vstorage https://github.com/Agoric/agoric-sdk/issues/9066
void E(topicKit.recorder).write('');
// XXX consider parsing local/remoteAddr to portId, channelId, counterpartyPortId, counterpartyChannelId, connectionId, counterpartyConnectionId
// FIXME these values will not update if IcaAccount gets new values after reopening.
// consider having IcaAccount responsible for the owning the writer. It might choose to share it with COA.
void E(topicKit.recorder).write(
/** @type {CosmosOrchestrationAccountStorageState} */ ({
localAddress,
remoteAddress,
}),
);

return { chainAddress, bondDenom, topicKit, ...rest };
return {
chainAddress,
bondDenom,
localAddress,
remoteAddress,
topicKit,
...rest,
};
},
{
helper: {
Expand Down
73 changes: 46 additions & 27 deletions packages/orchestration/src/exos/remote-chain-facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ChainAddressShape, ChainFacadeI } from '../typeGuards.js';
* @import {TimerService} from '@agoric/time';
* @import {Remote} from '@agoric/internal';
* @import {Vow, VowTools} from '@agoric/vow';
* @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js';
* @import {CosmosInterchainService} from './cosmos-interchain-service.js';
* @import {prepareCosmosOrchestrationAccount} from './cosmos-orchestration-account.js';
* @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, ChainAddress, IcaAccount, Denom, Chain} from '../types.js';
Expand Down Expand Up @@ -48,7 +49,7 @@ const prepareRemoteChainFacadeKit = (
// consider making an `accounts` childNode
storageNode,
timer,
vowTools: { asVow, watch },
vowTools: { asVow, watch, allVows },
},
) =>
zone.exoClassKit(
Expand All @@ -60,18 +61,19 @@ const prepareRemoteChainFacadeKit = (
.optional(M.arrayOf(M.undefined())) // empty context
.returns(VowShape),
}),
getAddressWatcher: M.interface('getAddressWatcher', {
onFulfilled: M.call(M.record())
.optional(M.remotable())
.returns(VowShape),
getAddressesWatcher: M.interface('getAddressesWatcher', {
onFulfilled: M.call(
[ChainAddressShape, M.string(), M.string()],
M.remotable(),
).returns(VowShape),
}),
makeChildNodeWatcher: M.interface('makeChildNodeWatcher', {
onFulfilled: M.call(M.remotable())
.optional({
account: M.remotable(),
chainAddress: ChainAddressShape,
})
.returns(M.remotable()),
onFulfilled: M.call(M.remotable(), {
account: M.remotable(),
chainAddress: ChainAddressShape,
localAddress: M.string(),
remoteAddress: M.string(),
}).returns(M.remotable()),
}),
},
/**
Expand Down Expand Up @@ -117,22 +119,26 @@ const prepareRemoteChainFacadeKit = (
*/
onFulfilled(account) {
return watch(
E(account).getAddress(),
this.facets.getAddressWatcher,
allVows([
E(account).getAddress(),
E(account).getLocalAddress(),
E(account).getRemoteAddress(),
]),
this.facets.getAddressesWatcher,
account,
);
},
},
getAddressWatcher: {
getAddressesWatcher: {
/**
* @param {ChainAddress} chainAddress
* @param {[ChainAddress, LocalIbcAddress, RemoteIbcAddress]} chainAddresses
* @param {IcaAccount} account
*/
onFulfilled(chainAddress, account) {
onFulfilled([chainAddress, localAddress, remoteAddress], account) {
return watch(
E(storageNode).makeChildNode(chainAddress.value),
this.facets.makeChildNodeWatcher,
{ account, chainAddress },
{ account, chainAddress, localAddress, remoteAddress },
);
},
},
Expand All @@ -142,23 +148,36 @@ const prepareRemoteChainFacadeKit = (
* @param {{
* account: IcaAccount;
* chainAddress: ChainAddress;
* localAddress: LocalIbcAddress;
* remoteAddress: RemoteIbcAddress;
* }} ctx
*/
onFulfilled(childNode, { account, chainAddress }) {
onFulfilled(
childNode,
{ account, chainAddress, localAddress, remoteAddress },
) {
const { remoteChainInfo } = this.state;
const stakingDenom = remoteChainInfo.stakingTokens?.[0]?.denom;
if (!stakingDenom) {
throw Fail`chain info lacks staking denom`;
}
return makeCosmosOrchestrationAccount(chainAddress, stakingDenom, {
account,
// FIXME storage path https://github.com/Agoric/agoric-sdk/issues/9066
storageNode: childNode,
// FIXME provide real ICQ connection
// FIXME make Query Connection available via chain, not orchestrationAccount
icqConnection: anyVal,
timer,
});
return makeCosmosOrchestrationAccount(
{
chainAddress,
bondDenom: stakingDenom,
localAddress,
remoteAddress,
},
{
account,
// FIXME storage path https://github.com/Agoric/agoric-sdk/issues/9066
storageNode: childNode,
// FIXME provide real ICQ connection
// FIXME make Query Connection available via chain, not orchestrationAccount
icqConnection: anyVal,
timer,
},
);
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ Generated by [AVA](https://avajs.dev).
Object @Map Iterator {
[
'mockChainStorageRoot.stakeAtom.accounts.cosmos1test',
'{"body":"#\\"\\"","slots":[]}',
'{"body":"#{\\"localAddress\\":\\"/ibc-port/icacontroller-1/ordered/{\\\\\\"version\\\\\\":\\\\\\"ics27-1\\\\\\",\\\\\\"controllerConnectionId\\\\\\":\\\\\\"connection-8\\\\\\",\\\\\\"hostConnectionId\\\\\\":\\\\\\"connection-649\\\\\\",\\\\\\"address\\\\\\":\\\\\\"cosmos1test\\\\\\",\\\\\\"encoding\\\\\\":\\\\\\"proto3\\\\\\",\\\\\\"txType\\\\\\":\\\\\\"sdk_multi_msg\\\\\\"}/ibc-channel/channel-0\\",\\"remoteAddress\\":\\"/ibc-hop/connection-8/ibc-port/icahost/ordered/{\\\\\\"version\\\\\\":\\\\\\"ics27-1\\\\\\",\\\\\\"controllerConnectionId\\\\\\":\\\\\\"connection-8\\\\\\",\\\\\\"hostConnectionId\\\\\\":\\\\\\"connection-649\\\\\\",\\\\\\"address\\\\\\":\\\\\\"cosmos1test\\\\\\",\\\\\\"encoding\\\\\\":\\\\\\"proto3\\\\\\",\\\\\\"txType\\\\\\":\\\\\\"sdk_multi_msg\\\\\\"}/ibc-channel/channel-0\\"}","slots":[]}',
],
[
'mockChainStorageRoot.stakeAtom.accounts.cosmos1test1',
'{"body":"#\\"\\"","slots":[]}',
'{"body":"#{\\"localAddress\\":\\"/ibc-port/icacontroller-2/ordered/{\\\\\\"version\\\\\\":\\\\\\"ics27-1\\\\\\",\\\\\\"controllerConnectionId\\\\\\":\\\\\\"connection-8\\\\\\",\\\\\\"hostConnectionId\\\\\\":\\\\\\"connection-649\\\\\\",\\\\\\"address\\\\\\":\\\\\\"cosmos1test1\\\\\\",\\\\\\"encoding\\\\\\":\\\\\\"proto3\\\\\\",\\\\\\"txType\\\\\\":\\\\\\"sdk_multi_msg\\\\\\"}/ibc-channel/channel-1\\",\\"remoteAddress\\":\\"/ibc-hop/connection-8/ibc-port/icahost/ordered/{\\\\\\"version\\\\\\":\\\\\\"ics27-1\\\\\\",\\\\\\"controllerConnectionId\\\\\\":\\\\\\"connection-8\\\\\\",\\\\\\"hostConnectionId\\\\\\":\\\\\\"connection-649\\\\\\",\\\\\\"address\\\\\\":\\\\\\"cosmos1test1\\\\\\",\\\\\\"encoding\\\\\\":\\\\\\"proto3\\\\\\",\\\\\\"txType\\\\\\":\\\\\\"sdk_multi_msg\\\\\\"}/ibc-channel/channel-1\\"}","slots":[]}',
],
[
'mockChainStorageRoot.stakeOsmo.accounts.osmo1test2',
'{"body":"#\\"\\"","slots":[]}',
'{"body":"#{\\"localAddress\\":\\"/ibc-port/icacontroller-3/ordered/{\\\\\\"version\\\\\\":\\\\\\"ics27-1\\\\\\",\\\\\\"controllerConnectionId\\\\\\":\\\\\\"connection-1\\\\\\",\\\\\\"hostConnectionId\\\\\\":\\\\\\"connection-1649\\\\\\",\\\\\\"address\\\\\\":\\\\\\"osmo1test2\\\\\\",\\\\\\"encoding\\\\\\":\\\\\\"proto3\\\\\\",\\\\\\"txType\\\\\\":\\\\\\"sdk_multi_msg\\\\\\"}/ibc-channel/channel-2\\",\\"remoteAddress\\":\\"/ibc-hop/connection-1/ibc-port/icahost/ordered/{\\\\\\"version\\\\\\":\\\\\\"ics27-1\\\\\\",\\\\\\"controllerConnectionId\\\\\\":\\\\\\"connection-1\\\\\\",\\\\\\"hostConnectionId\\\\\\":\\\\\\"connection-1649\\\\\\",\\\\\\"address\\\\\\":\\\\\\"osmo1test2\\\\\\",\\\\\\"encoding\\\\\\":\\\\\\"proto3\\\\\\",\\\\\\"txType\\\\\\":\\\\\\"sdk_multi_msg\\\\\\"}/ibc-channel/channel-0\\"}","slots":[]}',
],
}
Binary file not shown.
14 changes: 12 additions & 2 deletions packages/orchestration/test/examples/stake-ica.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,26 @@ test('makeAccountInvitationMaker', async t => {
offerResult.publicSubscribers.account.subscriber,
);
const storageUpdate = await E(accountNotifier).getUpdateSince();
const expectedStorageValue = {
localAddress:
'/ibc-port/icacontroller-1/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-0',
remoteAddress:
'/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-0',
};

t.deepEqual(storageUpdate, {
updateCount: 1n,
value: '',
value: expectedStorageValue,
});

const vstorageEntry = bootstrap.storage.data.get(
'mockChainStorageRoot.stakeAtom.accounts.cosmos1test',
);
t.truthy(vstorageEntry, 'vstorage account entry created');
t.is(bootstrap.marshaller.fromCapData(JSON.parse(vstorageEntry!)), '');
t.deepEqual(
bootstrap.marshaller.fromCapData(JSON.parse(vstorageEntry!)),
expectedStorageValue,
);
});

test('CosmosOrchestrationAccount - not yet implemented', async t => {
Expand Down
21 changes: 14 additions & 7 deletions packages/orchestration/test/exos/make-test-coa-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,22 @@ export const prepareMakeTestCOAKit = (
controllerConnectionId,
);

const accountAddress = await E(cosmosOrchAccount).getAddress();
const [chainAddress, localAddress, remoteAddress] = await Promise.all([
E(cosmosOrchAccount).getAddress(),
E(cosmosOrchAccount).getLocalAddress(),
E(cosmosOrchAccount).getRemoteAddress(),
]);

t.log('make a CosmosOrchestrationAccount');
const holder = makeCosmosOrchestrationAccount(accountAddress, bondDenom, {
account: cosmosOrchAccount,
storageNode: storageNode.makeChildNode(accountAddress.value),
icqConnection: undefined,
timer,
});
const holder = makeCosmosOrchestrationAccount(
{ chainAddress, bondDenom, localAddress, remoteAddress },
{
account: cosmosOrchAccount,
storageNode: storageNode.makeChildNode(chainAddress.value),
icqConnection: undefined,
timer,
},
);

return holder;
};
Expand Down
Loading

0 comments on commit c227ce2

Please sign in to comment.