Skip to content

Commit

Permalink
feat: plumb localchain.queryMany through orchestrate facade (#9935)
Browse files Browse the repository at this point in the history
refs: #9890
refs: #9964

## Description
- Implements `LocalChainFacade.query()` via `LocalChain.queryMany()`
- Exports query types from `localchain.js`
- Add multichain (e2e) tests for `LocalChainFacade.query()` and `RemoteChainFacade.query()`
- Adds localchain query mocks to to `packages/vats/tools/fake-bridge.js` from observed values in simchain testing

### Security Considerations
n/a

### Scaling Considerations
n/a

### Documentation Considerations
Are the differences between `msgs` for local and remote chains clear enough? If not, how can we make it more clear?

### Testing Considerations


### Upgrade Considerations
Makes changes to localchain.js - but only to typedefs.
  • Loading branch information
mergify[bot] committed Aug 29, 2024
2 parents bd64eda + b116e2a commit 3386e64
Show file tree
Hide file tree
Showing 15 changed files with 602 additions and 60 deletions.
4 changes: 3 additions & 1 deletion multichain-testing/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ chains:
host_port: 'icqhost'
params:
host_enabled: true
allow_queries: ['*']
allow_queries:
- /cosmos.bank.v1beta1.Query/Balance
- /cosmos.bank.v1beta1.Query/AllBalances
faucet:
enabled: true
type: starship
Expand Down
300 changes: 300 additions & 0 deletions multichain-testing/test/chain-queries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
import anyTest from '@endo/ses-ava/prepare-endo.js';
import type { TestFn } from 'ava';
import {
QueryBalanceRequest,
QueryBalanceResponse,
QueryAllBalancesRequest,
QueryAllBalancesResponse,
} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js';
import { toRequestQueryJson, typedJson } from '@agoric/cosmic-proto';
import { decodeBase64 } from '@endo/base64';
import {
commonSetup,
SetupContextWithWallets,
chainConfig,
FAUCET_POUR,
} from './support.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import { createWallet } from '../tools/wallet.js';
import { makeQueryClient } from '../tools/query.js';

const test = anyTest as TestFn<SetupContextWithWallets>;

const accounts = ['osmosis', 'cosmoshub', 'agoric'];

const contractName = 'basicFlows';
const contractBuilder =
'../packages/builders/scripts/orchestration/init-basic-flows.js';

test.before(async t => {
const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t);
deleteTestKeys(accounts).catch();
const wallets = await setupTestKeys(accounts);
t.context = { ...rest, wallets, deleteTestKeys };
const { startContract } = rest;
await startContract(contractName, contractBuilder);
});

test.after(async t => {
const { deleteTestKeys } = t.context;
deleteTestKeys(accounts);
});

const queryICQChain = test.macro({
title: (_, chainName: string) => `Send ICQ Query on ${chainName}`,
exec: async (t, chainName: string) => {
const config = chainConfig[chainName];
if (!config) return t.fail(`Unknown chain: ${chainName}`);
const {
wallets,
provisionSmartWallet,
vstorageClient,
retryUntilCondition,
useChain,
} = t.context;

const { creditFromFaucet, chainInfo, getRestEndpoint } =
useChain(chainName);
const { staking, bech32_prefix } = chainInfo.chain;
const denom = staking?.staking_tokens?.[0].denom;
if (!denom) throw Error(`no denom for ${chainName}`);

t.log(
'Set up wallet with tokens so we have a wallet with balance to query',
);
const wallet = await createWallet(bech32_prefix);
const { address } = (await wallet.getAccounts())[0];
await creditFromFaucet(address);

const remoteQueryClient = makeQueryClient(await getRestEndpoint());
await retryUntilCondition(
() => remoteQueryClient.queryBalances(address),
({ balances }) => Number(balances?.[0]?.amount) > 0,
`Faucet balances found for ${address}`,
);

const agoricAddr = wallets[chainName];
const wdUser1 = await provisionSmartWallet(agoricAddr, {
BLD: 100n,
IST: 100n,
});
t.log(`provisioning agoric smart wallet for ${agoricAddr}`);

const doOffer = makeDoOffer(wdUser1);
t.log(`${chainName} sendICQQuery offer`);
const offerId = `${chainName}-sendICQQuery-${Date.now()}`;

const balanceQuery = toRequestQueryJson(
QueryBalanceRequest.toProtoMsg({
address,
denom,
}),
);
const allBalanceQuery = toRequestQueryJson(
QueryAllBalancesRequest.toProtoMsg({
address,
}),
);

await doOffer({
id: offerId,
invitationSpec: {
source: 'agoricContract',
instancePath: [contractName],
callPipe: [['makeSendICQQueryInvitation']],
},
offerArgs: { chainName, msgs: [balanceQuery, allBalanceQuery] },
proposal: {},
});

const offerResult = await retryUntilCondition(
() => vstorageClient.queryData(`published.wallet.${agoricAddr}`),
({ status }) => status.id === offerId && (status.result || status.error),
`${offerId} continuing invitation is in vstorage`,
{
maxRetries: 15,
},
);
t.log('ICQ Query Offer Result', offerResult);
const {
status: { result, error },
} = offerResult;
t.is(error, undefined, 'No error observed');

const [balanceQueryResult, allBalanceQueryResult] = JSON.parse(result);

t.is(balanceQueryResult.code, 0, 'balance query was successful');
const balanceQueryResultDecoded = QueryBalanceResponse.decode(
decodeBase64(balanceQueryResult.key),
);
t.log('balanceQueryResult', balanceQueryResultDecoded);
t.deepEqual(balanceQueryResultDecoded, {
balance: {
amount: String(FAUCET_POUR),
denom,
},
});

t.is(allBalanceQueryResult.code, 0, 'allBalances query was successful');
const allBalanceQueryResultDecoded = QueryAllBalancesResponse.decode(
decodeBase64(allBalanceQueryResult.key),
);
t.log('allBalanceQueryResult', allBalanceQueryResultDecoded);
t.like(allBalanceQueryResultDecoded, {
balances: [
{
amount: String(FAUCET_POUR),
denom,
},
],
});
},
});

const queryChainWithoutICQ = test.macro({
title: (_, chainName: string) =>
`Attempt Query on chain with ICQ ${chainName}`,
exec: async (t, chainName: string) => {
const config = chainConfig[chainName];
if (!config) return t.fail(`Unknown chain: ${chainName}`);
const {
wallets,
provisionSmartWallet,
vstorageClient,
retryUntilCondition,
useChain,
} = t.context;

const { chainInfo } = useChain(chainName);
const { staking, chain_id } = chainInfo.chain;
const denom = staking?.staking_tokens?.[0].denom;
if (!denom) throw Error(`no denom for ${chainName}`);

const agoricAddr = wallets[chainName];
const wdUser1 = await provisionSmartWallet(agoricAddr, {
BLD: 100n,
IST: 100n,
});
t.log(`provisioning agoric smart wallet for ${agoricAddr}`);

const doOffer = makeDoOffer(wdUser1);
t.log(`${chainName} sendICQQuery offer (unsupported)`);
const offerId = `${chainName}-sendICQQuery-${Date.now()}`;

const balanceQuery = toRequestQueryJson(
QueryBalanceRequest.toProtoMsg({
address: 'cosmos1234',
denom,
}),
);

await doOffer({
id: offerId,
invitationSpec: {
source: 'agoricContract',
instancePath: [contractName],
callPipe: [['makeSendICQQueryInvitation']],
},
offerArgs: { chainName, msgs: [balanceQuery] },
proposal: {},
});

const offerResult = await retryUntilCondition(
() => vstorageClient.queryData(`published.wallet.${agoricAddr}`),
({ status }) => status.id === offerId && (status.result || status.error),
`${offerId} continuing invitation is in vstorage`,
{
maxRetries: 10,
},
);
t.is(
offerResult.status.error,
`Error: Queries not available for chain "${chain_id}"`,
'Queries not available error returned',
);
},
});

test.serial('Send Local Query from chain object', async t => {
const { wallets, provisionSmartWallet, vstorageClient, retryUntilCondition } =
t.context;

const agoricAddr = wallets['agoric'];
const wdUser1 = await provisionSmartWallet(agoricAddr, {
BLD: 100n,
IST: 100n,
});
const expectedBalances = [
{
denom: 'ubld',
amount: '90000000', // 100n * (10n ** 6n) - smart wallet provision
},
{
denom: 'uist',
amount: '100250000', // 100n * (10n ** 6n) + smart wallet credit
},
];
t.log(`provisioning agoric smart wallet for ${agoricAddr}`);

const doOffer = makeDoOffer(wdUser1);
t.log('sendLocalQuery offer');
const offerId = `agoric-sendLocalQuery-${Date.now()}`;

const allBalancesProto3JsonQuery = typedJson(
'/cosmos.bank.v1beta1.QueryAllBalancesRequest',
{
address: agoricAddr,
},
);
const balanceProto3JsonQuery = typedJson(
'/cosmos.bank.v1beta1.QueryBalanceRequest',
{
address: agoricAddr,
denom: 'ubld',
},
);

await doOffer({
id: offerId,
invitationSpec: {
source: 'agoricContract',
instancePath: [contractName],
callPipe: [['makeSendLocalQueryInvitation']],
},
offerArgs: {
msgs: [allBalancesProto3JsonQuery, balanceProto3JsonQuery],
},
proposal: {},
});

const offerResult = await retryUntilCondition(
() => vstorageClient.queryData(`published.wallet.${agoricAddr}`),
({ status }) => status.id === offerId && (status.result || status.error),
`${offerId} continuing invitation is in vstorage`,
{
maxRetries: 10,
},
);

const parsedResults = JSON.parse(offerResult.status.result);
t.truthy(parsedResults[0].height, 'query height is returned');
t.is(parsedResults[0].error, '', 'error is empty');
t.like(
parsedResults[0].reply,
{
balances: expectedBalances,
},
'QueryAllBalances result is returned',
);
t.deepEqual(
parsedResults[1].reply,
{
'@type': '/cosmos.bank.v1beta1.QueryBalanceResponse',
balance: expectedBalances[0],
},
'QueryBalance result is returned',
);
});

test.serial(queryICQChain, 'osmosis');
test.serial(queryChainWithoutICQ, 'cosmoshub');
8 changes: 5 additions & 3 deletions multichain-testing/test/stake-ica.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import anyTest from '@endo/ses-ava/prepare-endo.js';
import type { TestFn } from 'ava';
import { commonSetup, SetupContextWithWallets } from './support.js';
import {
commonSetup,
SetupContextWithWallets,
FAUCET_POUR,
} from './support.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import { makeQueryClient } from '../tools/query.js';
import { sleep, type RetryOptions } from '../tools/sleep.js';
Expand Down Expand Up @@ -35,8 +39,6 @@ test.before(async t => {
t.context = { ...rest, wallets, deleteTestKeys };
});

const FAUCET_POUR = 10000000000n;

test.after(async t => {
const { deleteTestKeys } = t.context;
deleteTestKeys(accounts);
Expand Down
2 changes: 2 additions & 0 deletions multichain-testing/test/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { makeRetryUntilCondition } from '../tools/sleep.js';
import { makeDeployBuilder } from '../tools/deploy.js';
import { makeHermes } from '../tools/hermes-tools.js';

export const FAUCET_POUR = 10_000n * 1_000_000n;

const setupRegistry = makeSetupRegistry(makeGetFile({ dirname, join }));

// XXX consider including bech32Prefix in `ChainInfo`
Expand Down
4 changes: 4 additions & 0 deletions packages/cosmic-proto/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import type {
import type {
QueryAllBalancesRequest,
QueryAllBalancesResponse,
QueryBalanceRequest,
QueryBalanceRequestProtoMsg,
QueryBalanceResponse,
} from './codegen/cosmos/bank/v1beta1/query.js';
import type {
MsgSend,
Expand Down Expand Up @@ -38,6 +40,8 @@ export type Proto3Shape = {
'/cosmos.bank.v1beta1.MsgSendResponse': MsgSendResponse;
'/cosmos.bank.v1beta1.QueryAllBalancesRequest': QueryAllBalancesRequest;
'/cosmos.bank.v1beta1.QueryAllBalancesResponse': QueryAllBalancesResponse;
'/cosmos.bank.v1beta1.QueryBalanceRequest': QueryBalanceRequest;
'/cosmos.bank.v1beta1.QueryBalanceResponse': QueryBalanceResponse;
'/cosmos.staking.v1beta1.MsgDelegate': MsgDelegate;
'/cosmos.staking.v1beta1.MsgDelegateResponse': MsgDelegateResponse;
'/cosmos.staking.v1beta1.MsgUndelegate': MsgUndelegate;
Expand Down
9 changes: 8 additions & 1 deletion packages/orchestration/src/examples/basic-flows.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const contract = async (
makeSendICQQueryInvitation: M.callWhen().returns(InvitationShape),
makeAccountAndSendBalanceQueryInvitation:
M.callWhen().returns(InvitationShape),
makeSendLocalQueryInvitation: M.callWhen().returns(InvitationShape),
}),
{
makeOrchAccountInvitation() {
Expand All @@ -59,7 +60,7 @@ const contract = async (
},
makeSendICQQueryInvitation() {
return zcf.makeInvitation(
orchFns.sendQuery,
orchFns.sendICQQuery,
'Submit a query to a remote chain',
);
},
Expand All @@ -69,6 +70,12 @@ const contract = async (
'Make an account and submit a balance query',
);
},
makeSendLocalQueryInvitation() {
return zcf.makeInvitation(
orchFns.sendLocalQuery,
'Submit a query to the local chain',
);
},
},
);

Expand Down
Loading

0 comments on commit 3386e64

Please sign in to comment.