Skip to content

Commit

Permalink
new client-utils package (#10407)
Browse files Browse the repository at this point in the history
closes: #10168

## Description
Provide a new package, `client-utils`, as a home for utilities that are useful to clients of an Agoric chain. This doesn't currently use `@agoric/rpc` but over time some of it may be pushed down into that package. 

Related work…

- #9200 
This will be where those client factories are kept.

- #10369

This will solve most of what we need for functional testing. Some aspects are specific to the A3P synthetic chain (like account addresses and references to history) but most of what the tests are doing with the chain are operations that any client might do.

- #9109
- #8963

This will contribute to those goals.


### Security Considerations

Reduces authority needed to query chain (from child_process to fetch)

### Scaling Considerations
This is a big package, but it's not to be run on chain. Most client apps use some form of code shaking so they'll only take what they need.

### Documentation Considerations
Once this settles down it ought to be a part of docs.agoric.com

### Testing Considerations
The only test yet is a live one to make sure a query to Emerynet for Swingset params succeeds as expected, even under SES.

No package CI yet. Mostly it's refactoring of existing code so those uses serve as coverage. I do think this would benefit from some additional testing.

### Upgrade Considerations
Will never be on chain
  • Loading branch information
mergify[bot] authored Nov 6, 2024
2 parents 8943c95 + c3f5438 commit d4f2864
Show file tree
Hide file tree
Showing 36 changed files with 507 additions and 223 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test-all-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ jobs:
- name: yarn test (casting)
if: (success() || failure())
run: cd packages/casting && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (client-utils)
if: (success() || failure())
run: cd packages/client-utils && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (create-dapp)
run: cd packages/create-dapp && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (fast-usdc)
Expand Down
1 change: 1 addition & 0 deletions packages/agoric-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@endo/errors": "^1.2.7",
"@agoric/cache": "^0.3.2",
"@agoric/casting": "^0.4.2",
"@agoric/client-utils": "^0.1.0",
"@agoric/cosmic-proto": "^0.4.0",
"@agoric/ertp": "^0.16.2",
"@agoric/governance": "^0.10.3",
Expand Down
12 changes: 9 additions & 3 deletions packages/agoric-cli/src/commands/auction.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// @ts-check

/* eslint-env node */
import { InvalidArgumentError } from 'commander';
import { Fail } from '@endo/errors';
import { makeRpcUtils } from '../lib/rpc.js';
import { makeRpcUtils } from '@agoric/client-utils';
import { outputActionAndHint } from '../lib/wallet.js';
import { getNetworkConfig } from '../lib/network-config.js';

/**
* @import {ParamTypesMap, ParamTypesMapFromRecord} from '@agoric/governance/src/contractGovernance/typedParamManager.js'
* @import {ParamValueForType} from '@agoric/governance/src/types.js'
*/

const networkConfig = await getNetworkConfig({ env: process.env, fetch });

/**
* @template {ParamTypesMap} M
* @typedef {{
Expand Down Expand Up @@ -86,7 +89,10 @@ export const makeAuctionCommand = (
* }} opts
*/
async opts => {
const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch });
const { agoricNames, readLatestHead } = await makeRpcUtils(
{ fetch },
networkConfig,
);

/** @type {{ current: AuctionParamRecord }} */
// @ts-expect-error XXX should runtime check?
Expand Down
25 changes: 16 additions & 9 deletions packages/agoric-cli/src/commands/gov.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @ts-check
/* eslint-disable func-names */
/* global globalThis, process, setTimeout */
/* eslint-env node */
import { makeRpcUtils } from '@agoric/client-utils';
import { execFileSync as execFileSyncAmbient } from 'child_process';
import { Command, CommanderError } from 'commander';
import { normalizeAddressWithOptions, pollBlocks } from '../lib/chain.js';
import { getNetworkConfig, makeRpcUtils } from '../lib/rpc.js';
import { getNetworkConfig } from '../lib/network-config.js';
import {
findContinuingIds,
getCurrent,
Expand All @@ -25,6 +26,8 @@ const collectValues = (val, memo) => {

const defaultKeyring = process.env.AGORIC_KEYRING_BACKEND || 'test';

const networkConfig = await getNetworkConfig({ env: process.env, fetch });

/**
* @param {import('anylogger').Logger} _logger
* @param {{
Expand All @@ -40,10 +43,9 @@ export const makeGovCommand = (_logger, io = {}) => {
const {
// Allow caller to provide access explicitly, but
// default to conventional ambient IO facilities.
env = process.env,
stdout = process.stdout,
stderr = process.stderr,
fetch = globalThis.fetch,
fetch = global.fetch,
execFileSync = execFileSyncAmbient,
delay = ms => new Promise(resolve => setTimeout(resolve, ms)),
} = io;
Expand Down Expand Up @@ -95,8 +97,7 @@ export const makeGovCommand = (_logger, io = {}) => {
{ toOffer, sendFrom, keyringBackend },
optUtils,
) {
const networkConfig = await getNetworkConfig(env);
const utils = await (optUtils || makeRpcUtils({ fetch }));
const utils = await (optUtils || makeRpcUtils({ fetch }, networkConfig));
const { agoricNames, readLatestHead } = utils;

assert(keyringBackend, 'missing keyring-backend option');
Expand Down Expand Up @@ -264,7 +265,10 @@ export const makeGovCommand = (_logger, io = {}) => {
)
.requiredOption('--for <string>', 'description of the invitation')
.action(async opts => {
const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch });
const { agoricNames, readLatestHead } = await makeRpcUtils(
{ fetch },
networkConfig,
);
const current = await getCurrent(opts.from, { readLatestHead });

const known = findContinuingIds(current, agoricNames);
Expand All @@ -290,7 +294,10 @@ export const makeGovCommand = (_logger, io = {}) => {
normalizeAddress,
)
.action(async opts => {
const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch });
const { agoricNames, readLatestHead } = await makeRpcUtils(
{ fetch },
networkConfig,
);
const current = await getCurrent(opts.from, { readLatestHead });

const found = findContinuingIds(current, agoricNames);
Expand Down Expand Up @@ -326,7 +333,7 @@ export const makeGovCommand = (_logger, io = {}) => {
normalizeAddress,
)
.action(async function (opts, options) {
const utils = await makeRpcUtils({ fetch });
const utils = await makeRpcUtils({ fetch }, networkConfig);
const { readLatestHead } = utils;

const info = await readLatestHead(
Expand Down
21 changes: 8 additions & 13 deletions packages/agoric-cli/src/commands/inter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,20 @@

// @ts-check
import { CommanderError, InvalidArgumentError } from 'commander';
// TODO: should get M from endo https://github.com/Agoric/agoric-sdk/issues/7090
import { makeWalletUtils } from '@agoric/client-utils';
import { makeOfferSpecShape } from '@agoric/inter-protocol/src/auction/auctionBook.js';
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
import { objectMap } from '@agoric/internal';
import { M, matches } from '@agoric/store';
import { M, matches } from '@endo/patterns';

import { normalizeAddressWithOptions, pollBlocks } from '../lib/chain.js';
import { getNetworkConfig } from '../lib/network-config.js';
import { getCurrent, outputActionAndHint, sendAction } from '../lib/wallet.js';
import {
asBoardRemote,
bigintReplacer,
makeAmountFormatter,
bigintReplacer,
} from '../lib/format.js';
import { getNetworkConfig } from '../lib/rpc.js';
import {
getCurrent,
makeWalletUtils,
outputActionAndHint,
sendAction,
} from '../lib/wallet.js';

const { values } = Object;

Expand Down Expand Up @@ -238,8 +233,8 @@ export const makeInterCommand = (
try {
// XXX pass fetch to getNetworkConfig() explicitly
// await null above makes this await safe
const networkConfig = await getNetworkConfig(env);
return makeWalletUtils({ fetch, execFileSync, delay }, networkConfig);
const networkConfig = await getNetworkConfig({ env, fetch });
return makeWalletUtils({ fetch, delay }, networkConfig);
} catch (err) {
// CommanderError is a class constructor, and so
// must be invoked with `new`.
Expand Down Expand Up @@ -334,7 +329,7 @@ inter auction status
* @param {string} from
* @param {import('@agoric/smart-wallet/src/offers.js').OfferSpec} offer
* @param {Awaited<ReturnType<tryMakeUtils>>} tools
* @param {boolean?} dryRun
* @param {boolean | undefined} dryRun
*/
const placeBid = async (from, offer, tools, dryRun = false) => {
const { networkConfig, agoricNames, pollOffer } = tools;
Expand Down
19 changes: 10 additions & 9 deletions packages/agoric-cli/src/commands/oracle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// @ts-check
/* eslint-disable func-names */
/* eslint-env node */
import {
makeRpcUtils,
makeWalletUtils,
storageHelper,
} from '@agoric/client-utils';
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
import { oracleBrandFeedName } from '@agoric/inter-protocol/src/proposals/utils.js';
import { Fail } from '@endo/errors';
Expand All @@ -9,15 +14,14 @@ import * as cp from 'child_process';
import { Command } from 'commander';
import { inspect } from 'util';
import { normalizeAddressWithOptions } from '../lib/chain.js';
import { bigintReplacer } from '../lib/format.js';
import { getNetworkConfig, makeRpcUtils, storageHelper } from '../lib/rpc.js';
import { getNetworkConfig } from '../lib/network-config.js';
import {
getCurrent,
makeWalletUtils,
outputAction,
sendAction,
sendHint,
} from '../lib/wallet.js';
import { bigintReplacer } from '../lib/format.js';

/** @import {PriceAuthority, PriceDescription, PriceQuote, PriceQuoteValue, PriceQuery,} from '@agoric/zoe/tools/types.js'; */

Expand Down Expand Up @@ -82,8 +86,8 @@ export const makeOracleCommand = (logger, io = {}) => {

const rpcTools = async () => {
// XXX pass fetch to getNetworkConfig() explicitly
const networkConfig = await getNetworkConfig(env);
const utils = await makeRpcUtils({ fetch });
const networkConfig = await getNetworkConfig({ env: process.env, fetch });
const utils = await makeRpcUtils({ fetch }, networkConfig);

const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => {
const name = oracleBrandFeedName(brandIn, brandOut);
Expand Down Expand Up @@ -267,10 +271,7 @@ export const makeOracleCommand = (logger, io = {}) => {
) => {
const { readLatestHead, networkConfig, lookupPriceAggregatorInstance } =
await rpcTools();
const wutil = await makeWalletUtils(
{ fetch, execFileSync, delay },
networkConfig,
);
const wutil = await makeWalletUtils({ fetch, delay }, networkConfig);
const unitPrice = scaleDecimals(price);

const feedPath = `published.priceFeed.${pair[0]}-${pair[1]}_price_feed`;
Expand Down
8 changes: 5 additions & 3 deletions packages/agoric-cli/src/commands/perf.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ import {
makeFollower,
makeLeaderFromRpcAddresses,
} from '@agoric/casting';
import { slotToRemotable } from '@agoric/internal/src/storage-test-utils.js';
import { boardSlottingMarshaller } from '@agoric/vats/tools/board-utils.js';
import { Command } from 'commander';
import fs from 'fs';
import { exit } from 'process';
import { slotToRemotable } from '@agoric/internal/src/storage-test-utils.js';
import { boardSlottingMarshaller } from '@agoric/vats/tools/board-utils.js';
import { makeLeaderOptions } from '../lib/casting.js';
import {
execSwingsetTransaction,
normalizeAddressWithOptions,
} from '../lib/chain.js';
import { networkConfig } from '../lib/rpc.js';
import { getNetworkConfig } from '../lib/network-config.js';

// tight for perf testing but less than this tends to hang.
const SLEEP_SECONDS = 0.1;

const networkConfig = await getNetworkConfig({ env: process.env, fetch });

/**
* @param {import('anylogger').Logger} logger
*/
Expand Down
11 changes: 7 additions & 4 deletions packages/agoric-cli/src/commands/psm.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// @ts-check
/* eslint-disable func-names */
/* eslint-env node */
import { Command } from 'commander';
import { makeRpcUtils, storageHelper } from '@agoric/client-utils';
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
import { asPercent } from '../lib/format.js';
import { makeRpcUtils, storageHelper } from '../lib/rpc.js';
import { Command } from 'commander';
import { getNetworkConfig } from '../lib/network-config.js';
import { outputExecuteOfferAction } from '../lib/wallet.js';
import { asPercent } from '../lib/format.js';

const networkConfig = await getNetworkConfig({ env: process.env, fetch });

// Adapted from https://gist.github.com/dckc/8b5b2f16395cb4d7f2ff340e0bc6b610#file-psm-tool

Expand Down Expand Up @@ -60,7 +63,7 @@ export const makePsmCommand = logger => {
);

const rpcTools = async () => {
const utils = await makeRpcUtils({ fetch });
const utils = await makeRpcUtils({ fetch }, networkConfig);

const lookupPsmInstance = ([minted, anchor]) => {
const name = `psm-${minted}-${anchor}`;
Expand Down
9 changes: 6 additions & 3 deletions packages/agoric-cli/src/commands/reserve.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// @ts-check
/* eslint-disable func-names */
/* eslint-env node */
import { makeRpcUtils } from '@agoric/client-utils';
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
import { Command } from 'commander';
import { makeRpcUtils } from '../lib/rpc.js';
import { getNetworkConfig } from '../lib/network-config.js';
import { outputActionAndHint } from '../lib/wallet.js';

const networkConfig = await getNetworkConfig({ env: process.env, fetch });

/**
* @param {import('anylogger').Logger} _logger
* @param {*} io
Expand All @@ -29,7 +32,7 @@ export const makeReserveCommand = (_logger, io = {}) => {
* }} opts
*/
async ({ collateralBrand, ...opts }) => {
const { agoricNames } = await makeRpcUtils({ fetch });
const { agoricNames } = await makeRpcUtils({ fetch }, networkConfig);

const offer = Offers.reserve.AddCollateral(agoricNames, {
collateralBrandKey: collateralBrand,
Expand Down Expand Up @@ -63,7 +66,7 @@ export const makeReserveCommand = (_logger, io = {}) => {
1,
)
.action(async function (opts) {
const { agoricNames } = await makeRpcUtils({ fetch });
const { agoricNames } = await makeRpcUtils({ fetch }, networkConfig);

const reserveInstance = agoricNames.instance.reserve;
assert(reserveInstance, 'missing reserve in names');
Expand Down
9 changes: 5 additions & 4 deletions packages/agoric-cli/src/commands/test-upgrade.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// @ts-check
/* eslint-env node */
import { makeWalletUtils } from '@agoric/client-utils';
import { Fail } from '@endo/errors';
import { CommanderError } from 'commander';
import { normalizeAddressWithOptions } from '../lib/chain.js';
import { getNetworkConfig } from '../lib/network-config.js';
import { sendAction } from '../lib/wallet.js';
import { bigintReplacer } from '../lib/format.js';
import { getNetworkConfig } from '../lib/rpc.js';
import { makeWalletUtils, sendAction } from '../lib/wallet.js';

/**
* Make commands for testing.
Expand Down Expand Up @@ -38,8 +39,8 @@ export const makeTestCommand = (
try {
// XXX pass fetch to getNetworkConfig() explicitly
// await null above makes this await safe
const networkConfig = await getNetworkConfig(env);
return makeWalletUtils({ fetch, execFileSync, delay }, networkConfig);
const networkConfig = await getNetworkConfig({ env, fetch });
return makeWalletUtils({ fetch, delay }, networkConfig);
} catch (err) {
// CommanderError is a class constructor, and so
// must be invoked with `new`.
Expand Down
Loading

0 comments on commit d4f2864

Please sign in to comment.