Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

save proposal from archive node #84

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/synthetic-chain/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env tsx

import assert from 'node:assert';
import { execSync } from 'node:child_process';
import path from 'node:path';
import { parseArgs } from 'node:util';
Expand All @@ -8,6 +9,7 @@ import {
buildProposalSubmissions,
readBuildConfig,
} from './src/cli/build.js';
import { saveProposalContents } from './src/cli/chain.ts';
import {
writeBakefileProposals,
writeDockerfile,
Expand All @@ -31,6 +33,7 @@ const allProposals = readProposals(root);

const { match } = values;
const proposals = match
// TODO match on proposal identifier too (or simply proposal path)
? allProposals.filter(p => p.proposalName.includes(match))
: allProposals;

Expand All @@ -43,6 +46,8 @@ build - build the synthetic-chain "use" images
test [--debug] - build the "test" images and run them
test -m <name> - target a particular proposal by substring match

save <id> - query resources from the proposal on chain and save to disk

doctor - diagnostics and quick fixes
`;

Expand Down Expand Up @@ -88,6 +93,13 @@ switch (cmd) {
}
}
break;
case 'save':
const id = positionals[1];
assert(id, 'must specify id to save');
const proposal = proposals.find(p => p.proposalIdentifier === id);
assert(proposal, `proposal ${id} not found`);
saveProposalContents(proposal);
break;
case 'doctor':
runDoctor(allProposals);
break;
Expand Down
114 changes: 114 additions & 0 deletions packages/synthetic-chain/src/cli/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import assert from 'node:assert';
import { isPassed, type ProposalInfo } from './proposals.js';
import { makeAgd } from '../lib/agd-lib.js';
import { execFileSync } from 'node:child_process';
import fsp from 'node:fs/promises';
import path from 'node:path';

const DEFAULT_ARCHIVE_NODE = 'https://main-a.rpc.agoric.net:443';

export function getArchiveNode() {
return process.env['ARCHIVE_NODE'] || DEFAULT_ARCHIVE_NODE;
}
const agdArchive = makeAgd({ execFileSync }).withOpts({
rpcAddrs: [getArchiveNode()],
});

type CoreEvalContent = {
'@type': '/agoric.swingset.CoreEvalProposal';
evals: Array<{ json_permits: string; js_code: string }>;
};

type ParameterChangeContent = {
'@type': '/cosmos.params.v1beta1.ParameterChangeProposal';
changes: Array<{
subspace: string;
key: string;
value: string;
}>;
};

export type QueryGovProposalResponse = {
proposal_id: string; // number as string
content: {
'@type': string; // XXX same type as in proposals.ts
title: string;
description: string;
// TODO import this type from agoric-sdk
evals: Array<{ json_permits: string; js_code: string }>;
} & (CoreEvalContent | ParameterChangeContent);
status: string; // XXX enum
final_tally_result: {
// each of these is a number
yes: string;
abstain: string;
no: string;
no_with_veto: string;
submit_time: string; // timestamp
deposit_end_time: string; // timestamp
total_deposit: [{ denom: 'ubld'; amount: string }];
voting_start_time: string; // timestamp
voting_end_time: string; // timestamp
};
};

async function fetchProposal(
proposal: ProposalInfo,
): Promise<QueryGovProposalResponse> {
return agdArchive.query(['gov', 'proposal', proposal.proposalIdentifier]);
}

export async function saveProposalContents(proposal: ProposalInfo) {
assert(isPassed(proposal), 'unpassed propoosals are not on the chain');
const data = await fetchProposal(proposal);
assert.equal(data.proposal_id, proposal.proposalIdentifier);
assert.equal(data.content['@type'], proposal.type);
assert.equal(data.status, 'PROPOSAL_STATUS_PASSED');
switch (proposal.type) {
case '/agoric.swingset.CoreEvalProposal':
const { evals } = data.content;
const submissionDir = path.join(
'proposals',
`${proposal.proposalIdentifier}:${proposal.proposalName}`,
'submission',
);
await fsp.mkdir(submissionDir, { recursive: true });
for (const { json_permits, js_code } of evals) {
await fsp.writeFile(
path.join(submissionDir, `${proposal.proposalName}.json`),
json_permits,
);
await fsp.writeFile(
path.join(submissionDir, `${proposal.proposalName}.js`),
js_code,
);
}
console.log(
'Proposal saved to',
submissionDir,
'. Now find these bundles and save them there too:',
);
// At this point we can trust the bundles because the js_code has the hash
// and SwingSet kernel verifies that the provided bundles match the hash in their filename.
for (const { js_code } of evals) {
const bundleIds = js_code.match(/b1-[a-z0-9]+/g);
console.log(bundleIds);
}
break;
case '/cosmos.params.v1beta1.ParameterChangeProposal':
const proposerRecord: { proposal_id: string; proposer: string } =
await agdArchive.query(['gov', 'proposer', proposal.proposalIdentifier]);
assert.equal(proposerRecord.proposal_id, proposal.proposalIdentifier);
const { proposer } = proposerRecord;
console.log(proposer);
const txHistory = await agdArchive.query([
'txs',
`--events=message.sender=${proposer}`,
]);
console.log(txHistory);
break;
case 'Software Upgrade Proposal':
console.warn('Nothing to save for Software Upgrade Proposal');
break;
}
}
15 changes: 10 additions & 5 deletions packages/synthetic-chain/src/cli/dockerfileGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

import fs from 'node:fs';
import {
lastPassedProposal,
encodeUpgradeInfo,
imageNameForProposal,
isPassed,
type CoreEvalProposal,
type ParameterChangeProposal,
type ProposalInfo,
type SoftwareUpgradeProposal,
encodeUpgradeInfo,
imageNameForProposal,
} from './proposals.js';

/**
Expand Down Expand Up @@ -102,7 +103,10 @@ RUN ./start_to_to.sh
* - Run the core-eval scripts from the proposal. They are only guaranteed to have started, not completed.
*/
EVAL(
{ proposalIdentifier, proposalName }: CoreEvalProposal,
{
proposalIdentifier,
proposalName,
}: CoreEvalProposal | ParameterChangeProposal,
lastProposal: ProposalInfo,
) {
return `
Expand Down Expand Up @@ -223,6 +227,7 @@ export function writeDockerfile(

switch (proposal.type) {
case '/agoric.swingset.CoreEvalProposal':
case '/cosmos.params.v1beta1.ParameterChangeProposal':
blocks.push(stage.EVAL(proposal, previousProposal!));
break;
case 'Software Upgrade Proposal':
Expand All @@ -247,7 +252,7 @@ export function writeDockerfile(
previousProposal = proposal;
}
// If one of the proposals is a passed proposal, make the latest one the default entrypoint
const lastPassed = lastPassedProposal(allProposals);
const lastPassed = allProposals.findLast(isPassed);
if (lastPassed) {
blocks.push(stage.DEFAULT(lastPassed));
}
Expand Down
19 changes: 12 additions & 7 deletions packages/synthetic-chain/src/cli/proposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ export type CoreEvalProposal = ProposalCommon & {
}
);

export type ProposalInfo = SoftwareUpgradeProposal | CoreEvalProposal;
export type ParameterChangeProposal = ProposalCommon & {
type: '/cosmos.params.v1beta1.ParameterChangeProposal';
};

export type ProposalInfo =
| SoftwareUpgradeProposal
| CoreEvalProposal
| ParameterChangeProposal;

function readInfo(proposalPath: string): ProposalInfo {
const packageJsonPath = path.join('proposals', proposalPath, 'package.json');
Expand Down Expand Up @@ -68,12 +75,6 @@ export const matchOneProposal = (
return proposals[0];
};

export function lastPassedProposal(
proposals: ProposalInfo[],
): ProposalInfo | undefined {
return proposals.findLast(p => p.proposalIdentifier.match(/^\d/));
}

export function imageNameForProposal(
proposal: Pick<ProposalCommon, 'proposalName'>,
stage: 'test' | 'use',
Expand All @@ -84,3 +85,7 @@ export function imageNameForProposal(
target,
};
}

export function isPassed(proposal: ProposalInfo) {
return proposal.proposalIdentifier.match(/^\d/);
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
// @ts-check
// @jessie-check
import { ExecFileSyncOptionsWithStringEncoding } from 'child_process';

const { freeze } = Object;

const agdBinary = 'agd';

/** @param {{ execFileSync: typeof import('child_process').execFileSync }} io */
export const makeAgd = ({ execFileSync }) => {
console.warn('XXX is sync IO essential?');

/** @param {{ home?: string, keyringBackend?: string, rpcAddrs?: string[] }} keyringOpts */
const make = ({ home, keyringBackend, rpcAddrs } = {}) => {
export const makeAgd = ({
execFileSync,
}: {
execFileSync: typeof import('child_process').execFileSync;
}) => {
const make = (
{ home, keyringBackend, rpcAddrs } = {} as {
home?: string;
keyringBackend?: string;
rpcAddrs?: string[];
},
) => {
const keyringArgs = [
...(home ? ['--home', home] : []),
...(keyringBackend ? [`--keyring-backend`, keyringBackend] : []),
];
console.warn('XXX: rpcAddrs after [0] are ignored');
// XXX: rpcAddrs after [0] are ignored
const nodeArgs = [...(rpcAddrs ? [`--node`, rpcAddrs[0]] : [])];

/**
* @param {string[]} args
* @param {*} [opts]
*/
const exec = (args, opts) => execFileSync(agdBinary, args, opts).toString();
const exec = (
args: string[],
opts?: ExecFileSyncOptionsWithStringEncoding,
) => execFileSync(agdBinary, args, opts).toString();

const outJson = ['--output', 'json'];

const ro = freeze({
status: async () => JSON.parse(exec([...nodeArgs, 'status'])),
/**
* @param {
* | [kind: 'tx', txhash: string]
* | [mod: 'vstorage', kind: 'data' | 'children', path: string]
* } qArgs
*/
query: async qArgs => {
query: async (
qArgs:
| [kind: 'gov', domain: string, ...rest: any]
| [kind: 'txs', filter: string]
| [kind: 'tx', txhash: string]
| [mod: 'vstorage', kind: 'data' | 'children', path: string],
) => {
const out = await exec(['query', ...qArgs, ...nodeArgs, ...outJson], {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
});

try {
return JSON.parse(out);
} catch (e) {
Expand Down Expand Up @@ -67,11 +73,15 @@ export const makeAgd = ({ execFileSync }) => {
const rw = freeze({
/**
* TODO: gas
*
* @param {string[]} txArgs
* @param {{ chainId: string, from: string, yes?: boolean }} opts
*/
tx: async (txArgs, { chainId, from, yes }) => {
tx: async (
txArgs: string[],
{
chainId,
from,
yes,
}: { chainId: string; from: string; yes?: boolean },
) => {
const yesArg = yes ? ['--yes'] : [];
const args = [
...nodeArgs,
Expand All @@ -97,15 +107,16 @@ export const makeAgd = ({ execFileSync }) => {
readOnly: () => ro,
nameHub: () => nameHub,
keys: {
add: (name, mnemonic) => {
add: (name: string, mnemonic: string) => {
return execFileSync(
agdBinary,
[...keyringArgs, 'keys', 'add', name, '--recover'],
{ input: mnemonic },
).toString();
},
},
withOpts: opts => make({ home, keyringBackend, rpcAddrs, ...opts }),
withOpts: (opts: Record<string, unknown>) =>
make({ home, keyringBackend, rpcAddrs, ...opts }),
});
return rw;
};
Expand Down
19 changes: 11 additions & 8 deletions packages/synthetic-chain/upgrade-test-scripts/install_deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ set -e

PROPOSAL_PATH=$1

if [ -z "$PROPOSAL_PATH" ]; then
echo "Must specify what proposal to install"
exit 1
fi

# The base image is Node 16.9, which supports Corepack.
# Yarn v4 requires Node 18+ but so far it's working with 16.19.
export YARN_IGNORE_NODE=1
Expand All @@ -14,12 +19,10 @@ corepack enable
cd "$(dirname "$(realpath -- "$0")")"

# TODO consider yarn workspaces to install all in one command
if [ -n "$PROPOSAL_PATH" ]; then
cd "../proposals/$PROPOSAL_PATH"

if test -f "yarn.lock"; then
yarn --version # only Berry supported, so next commands will fail on classic
yarn config set --home enableTelemetry 0
yarn install --immutable
fi
cd "../proposals/$PROPOSAL_PATH"

if test -f "yarn.lock"; then
yarn --version # only Berry supported, so next commands will fail on classic
yarn config set --home enableTelemetry 0
yarn install --immutable
fi
1 change: 1 addition & 0 deletions proposals/47:proposal-deposit/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
10 changes: 10 additions & 0 deletions proposals/47:proposal-deposit/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"agoricProposal": {
"type": "/cosmos.params.v1beta1.ParameterChangeProposal"
},
"type": "module",
"dependencies": {
"@agoric/synthetic-chain": "^0.0.4-2"
},
"packageManager": "[email protected]"
}
Loading
Loading