Skip to content

Commit

Permalink
feat: plumb icqConnection through orchestrate facade (#9927)
Browse files Browse the repository at this point in the history
closes: #9890

## Description
 - [x] ensure `CosmosOrchAccount` is given an `icqConnection` if `ChainConfig.icqEnabled` is true
 - [x] ensure `RemoteChainFacade` has `.query()` method available
 - ~~[ ] ensure `LocalChainFacade` has `.query()` method available~~ see #9935

### Security Considerations
n/a

### Scaling Considerations
n/a

### Documentation Considerations
n/a

### Testing Considerations
Includes unit tests. Multichain tests would be a nice addition, but I wouldn't consider them a blocker for the PR.

### Upgrade Considerations
n/a, code is currently unreleased
  • Loading branch information
mergify[bot] authored Aug 22, 2024
2 parents 03f13e7 + 8c28e67 commit 6d97bc4
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 74 deletions.
7 changes: 4 additions & 3 deletions packages/boot/test/orchestration/restart-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ test.serial('basicFlows', async t => {
},
proposal: {},
offerArgs: {
chainNames: ['agoric', 'cosmoshub'],
chainNames: ['agoric', 'cosmoshub', 'osmosis'],
},
});
// no errors and no result yet
Expand All @@ -242,7 +242,8 @@ test.serial('basicFlows', async t => {
result: undefined, // no property
},
});
t.is(getInboundQueueLength(), 2);
// 3x ICA Channel Opens, 1x ICQ Channel Open
t.is(getInboundQueueLength(), 4);

t.log('restart basicFlows');
await evalProposal(
Expand All @@ -264,7 +265,7 @@ test.serial('basicFlows', async t => {
result: 'UNPUBLISHED',
},
});
t.is(await flushInboundQueue(1), 1);
t.is(await flushInboundQueue(3), 3);
t.like(wallet.getLatestUpdateRecord(), {
status: {
id: id2,
Expand Down
10 changes: 9 additions & 1 deletion packages/orchestration/src/cosmos-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AnyJson, TypedJson } from '@agoric/cosmic-proto';
import type { AnyJson, TypedJson, JsonSafe } from '@agoric/cosmic-proto';
import type {
Delegation,
Redelegation,
Expand All @@ -11,6 +11,10 @@ import type {
Order,
} from '@agoric/cosmic-proto/ibc/core/channel/v1/channel.js';
import type { State as IBCConnectionState } from '@agoric/cosmic-proto/ibc/core/connection/v1/connection.js';
import type {
RequestQuery,
ResponseQuery,
} from '@agoric/cosmic-proto/tendermint/abci/types.js';
import type { Brand, Purse, Payment, Amount } from '@agoric/ertp/src/types.js';
import type { Port } from '@agoric/network';
import type { IBCChannelID, IBCConnectionID } from '@agoric/vats';
Expand Down Expand Up @@ -265,3 +269,7 @@ export type CosmosChainAccountMethods<CCI extends CosmosChainInfo> =
}
? StakingAccountActions
: {};

export type ICQQueryFunction = (
msgs: JsonSafe<RequestQuery>[],
) => Promise<JsonSafe<ResponseQuery>[]>;
15 changes: 15 additions & 0 deletions packages/orchestration/src/examples/basic-flows.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const contract = async (
M.interface('Basic Flows PF', {
makeOrchAccountInvitation: M.callWhen().returns(InvitationShape),
makePortfolioAccountInvitation: M.callWhen().returns(InvitationShape),
makeSendICQQueryInvitation: M.callWhen().returns(InvitationShape),
makeAccountAndSendBalanceQueryInvitation:
M.callWhen().returns(InvitationShape),
}),
{
makeOrchAccountInvitation() {
Expand All @@ -54,6 +57,18 @@ const contract = async (
'Make an Orchestration Account',
);
},
makeSendICQQueryInvitation() {
return zcf.makeInvitation(
orchFns.sendQuery,
'Submit a query to a remote chain',
);
},
makeAccountAndSendBalanceQueryInvitation() {
return zcf.makeInvitation(
orchFns.makeAccountAndSendBalanceQuery,
'Make an account and submit a balance query',
);
},
},
);

Expand Down
58 changes: 56 additions & 2 deletions packages/orchestration/src/examples/basic-flows.flows.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
* @file Primarily a testing fixture, but also serves as an example of how to
* leverage basic functionality of the Orchestration API with async-flow.
*/
import { Fail } from '@endo/errors';
import { M, mustMatch } from '@endo/patterns';

/**
* @import {Zone} from '@agoric/zone';
* @import {OrchestrationAccount, OrchestrationFlow, Orchestrator} from '@agoric/orchestration';
* @import {DenomArg, OrchestrationAccount, OrchestrationFlow, Orchestrator} from '@agoric/orchestration';
* @import {ResolvedPublicTopic} from '@agoric/zoe/src/contractSupport/topics.js';
* @import {JsonSafe} from '@agoric/cosmic-proto';
* @import {RequestQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js';
* @import {OrchestrationPowers} from '../utils/start-helper.js';
* @import {MakePortfolioHolder} from '../exos/portfolio-holder-kit.js';
* @import {OrchestrationTools} from '../utils/start-helper.js';
Expand Down Expand Up @@ -79,3 +81,55 @@ export const makePortfolioAccount = async (
return portfolioHolder.asContinuingOffer();
};
harden(makePortfolioAccount);

/**
* Send a query and get the response back in an offer result. This invitation is
* for testing only. In a real scenario it's better to use an RPC or API client
* and vstorage to retrieve data for a frontend. Queries should only be
* leveraged if contract logic requires it.
*
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {any} _ctx
* @param {ZCFSeat} seat
* @param {{ chainName: string; msgs: JsonSafe<RequestQuery>[] }} offerArgs
*/
export const sendQuery = async (orch, _ctx, seat, { chainName, msgs }) => {
seat.exit(); // no funds exchanged
mustMatch(chainName, M.string());
if (chainName === 'agoric') throw Fail`ICQ not supported on local chain`;
const remoteChain = await orch.getChain(chainName);
const queryResponse = await remoteChain.query(msgs);
console.debug('sendQuery response:', queryResponse);
return queryResponse;
};
harden(sendQuery);

/**
* Create an account and send a query and get the response back in an offer
* result. Like `sendQuery`, this invitation is for testing only. In a real
* scenario it doesn't make much sense to send a query immediately after the
* account is created - it won't have any funds.
*
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {any} _ctx
* @param {ZCFSeat} seat
* @param {{ chainName: string; denom: DenomArg }} offerArgs
*/
export const makeAccountAndSendBalanceQuery = async (
orch,
_ctx,
seat,
{ chainName, denom },
) => {
seat.exit(); // no funds exchanged
mustMatch(chainName, M.string());
if (chainName === 'agoric') throw Fail`ICQ not supported on local chain`;
const remoteChain = await orch.getChain(chainName);
const orchAccount = await remoteChain.makeAccount();
const queryResponse = await orchAccount.getBalance(denom);
console.debug('getBalance response:', queryResponse);
return queryResponse;
};
harden(makeAccountAndSendBalanceQuery);
33 changes: 14 additions & 19 deletions packages/orchestration/src/exos/icq-connection-kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,26 @@
import { Fail } from '@endo/errors';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { VowShape } from '@agoric/vow';
import { NonNullish, makeTracer } from '@agoric/internal';
import { makeQueryPacket, parseQueryPacket } from '../utils/packet.js';
import { OutboundConnectionHandlerI } from '../typeGuards.js';
import { ICQMsgShape, OutboundConnectionHandlerI } from '../typeGuards.js';

/**
* @import {Zone} from '@agoric/base-zone';
* @import {Connection, Port} from '@agoric/network';
* @import {Remote, VowTools} from '@agoric/vow';
* @import {Remote, Vow, VowTools} from '@agoric/vow';
* @import {JsonSafe} from '@agoric/cosmic-proto';
* @import {RequestQuery, ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js';
* @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js';
*/

const trace = makeTracer('Orchestration:ICQConnection');

export const ICQMsgShape = M.splitRecord(
{ path: M.string(), data: M.string() },
{ height: M.string(), prove: M.boolean() },
);

export const ICQConnectionI = M.interface('ICQConnection', {
getLocalAddress: M.call().returns(M.string()),
getRemoteAddress: M.call().returns(M.string()),
query: M.call(M.arrayOf(ICQMsgShape)).returns(M.promise()),
query: M.call(M.arrayOf(ICQMsgShape)).returns(VowShape),
});

/**
Expand Down Expand Up @@ -53,7 +49,7 @@ export const ICQConnectionI = M.interface('ICQConnection', {
* @param {Zone} zone
* @param {VowTools} vowTools
*/
export const prepareICQConnectionKit = (zone, { watch, when }) =>
export const prepareICQConnectionKit = (zone, { watch, asVow }) =>
zone.exoClassKit(
'ICQConnectionKit',
{
Expand Down Expand Up @@ -88,21 +84,20 @@ export const prepareICQConnectionKit = (zone, { watch, when }) =>
);
},
/**
* Vow rejects if packet fails to send or an error is returned
*
* @param {JsonSafe<RequestQuery>[]} msgs
* @returns {Promise<JsonSafe<ResponseQuery>[]>}
* @throws {Error} if packet fails to send or an error is returned
* @returns {Vow<JsonSafe<ResponseQuery>[]>}
*/
query(msgs) {
const { connection } = this.state;
// TODO #9281 do not throw synchronously when returning a promise; return a rejected Vow
/// see https://github.com/Agoric/agoric-sdk/pull/9454#discussion_r1626898694
if (!connection) throw Fail`connection not available`;
return when(
watch(
return asVow(() => {
const { connection } = this.state;
if (!connection) throw Fail`connection not available`;
return watch(
E(connection).send(makeQueryPacket(msgs)),
this.facets.parseQueryPacketWatcher,
),
);
);
});
},
},
parseQueryPacketWatcher: {
Expand Down
5 changes: 5 additions & 0 deletions packages/orchestration/src/exos/local-chain-facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { M } from '@endo/patterns';
import { pickFacet } from '@agoric/vat-data';
import { VowShape } from '@agoric/vow';

import { Fail } from '@endo/errors';
import { chainFacadeMethods } from '../typeGuards.js';

/**
Expand Down Expand Up @@ -107,6 +108,10 @@ const prepareLocalChainFacadeKit = (
this.facets.makeAccountWatcher,
);
},
query() {
// TODO https://github.com/Agoric/agoric-sdk/pull/9935
return asVow(() => Fail`not yet implemented`);
},
/** @type {HostOf<AgoricChainMethods['getVBankAssetInfo']>} */
getVBankAssetInfo() {
return asVow(() => {
Expand Down
Loading

0 comments on commit 6d97bc4

Please sign in to comment.