From bf676838f12ce7b900943ff86c2e524758584870 Mon Sep 17 00:00:00 2001 From: Jorge-Lopes Date: Tue, 5 Nov 2024 16:34:55 +0000 Subject: [PATCH 1/3] test(a3p): replace exitOffer shell script with js implementation --- .../proposals/z:acceptance/exitOffer.js | 98 ------------------- .../proposals/z:acceptance/wallet.test.js | 20 +++- 2 files changed, 15 insertions(+), 103 deletions(-) delete mode 100644 a3p-integration/proposals/z:acceptance/exitOffer.js diff --git a/a3p-integration/proposals/z:acceptance/exitOffer.js b/a3p-integration/proposals/z:acceptance/exitOffer.js deleted file mode 100644 index 959e9ed7c74..00000000000 --- a/a3p-integration/proposals/z:acceptance/exitOffer.js +++ /dev/null @@ -1,98 +0,0 @@ -// Note: limit imports to node modules for portability -/* eslint-env node */ -import { parseArgs, promisify } from 'node:util'; -import { execFile } from 'node:child_process'; -import { writeFile, mkdtemp, rm } from 'node:fs/promises'; -import { join } from 'node:path'; - -const options = /** @type {const} */ ({ - id: { type: 'string' }, - from: { type: 'string' }, - bin: { type: 'string', default: '/usr/src/agoric-sdk/node_modules/.bin' }, -}); - -const Usage = ` -Try to exit an offer, reclaiming any associated payments. - - node exitOffer.js --id ID --from FROM [--bin PATH] - -Options: - --id - --from
- - --bin default: ${options.bin.default} -`; - -const badUsage = () => { - const reason = new Error(Usage); - reason.name = 'USAGE'; - throw reason; -}; - -const { stringify: jq } = JSON; -// limited to JSON data: no remotables/promises; no undefined. -const toCapData = data => ({ body: `#${jq(data)}`, slots: [] }); - -const { entries } = Object; -/** - * @param {Record} obj - e.g. { color: 'blue' } - * @returns {string[]} - e.g. ['--color', 'blue'] - */ -const flags = obj => - entries(obj) - .map(([k, v]) => [`--${k}`, v]) - .flat(); - -const execP = promisify(execFile); - -const showAndRun = (file, args) => { - console.log('$', file, ...args); - return execP(file, args); -}; - -const withTempFile = async (tail, fn) => { - const tmpDir = await mkdtemp('offers-'); - const tmpFile = join(tmpDir, tail); - try { - const result = await fn(tmpFile); - return result; - } finally { - await rm(tmpDir, { recursive: true, force: true }).catch(err => - console.error(err), - ); - } -}; - -const doAction = async (action, from) => { - await withTempFile('offer.json', async tmpOffer => { - await writeFile(tmpOffer, jq(toCapData(action))); - - const out = await showAndRun('agoric', [ - 'wallet', - ...flags({ 'keyring-backend': 'test' }), - 'send', - ...flags({ offer: tmpOffer, from }), - ]); - return out.stdout; - }); -}; - -const main = async (argv, env) => { - const { values } = parseArgs({ args: argv.slice(2), options }); - const { id: offerId, from, bin } = values; - (offerId && from) || badUsage(); - - env.PATH = `${bin}:${env.PATH}`; - const action = { method: 'tryExitOffer', offerId }; - const out = await doAction(action, from); - console.log(out); -}; - -main(process.argv, process.env).catch(e => { - if (e.name === 'USAGE' || e.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') { - console.error(e.message); - } else { - console.error(e); - } - process.exit(1); -}); diff --git a/a3p-integration/proposals/z:acceptance/wallet.test.js b/a3p-integration/proposals/z:acceptance/wallet.test.js index 1c3e80395e8..f0dedd24503 100644 --- a/a3p-integration/proposals/z:acceptance/wallet.test.js +++ b/a3p-integration/proposals/z:acceptance/wallet.test.js @@ -1,3 +1,5 @@ +/* global fetch setTimeout */ + import test from 'ava'; import '@endo/init'; import { @@ -8,12 +10,19 @@ import { CHAINID, waitForBlock, } from '@agoric/synthetic-chain'; -import { $ } from 'execa'; +import { execFileSync } from 'child_process'; import { agd, getBalances, replaceTemplateValuesInFile, } from './test-lib/utils.js'; +import { makeWalletUtils } from './test-lib/wallet.js'; +import { networkConfig } from './test-lib/index.js'; + +const walletUtils = await makeWalletUtils( + { setTimeout, execFileSync, fetch }, + networkConfig, +); test.serial(`send invitation via namesByAddress`, async t => { const SUBMISSION_DIR = 'invitation-test-submission'; @@ -43,13 +52,14 @@ test.serial(`send invitation via namesByAddress`, async t => { }); test.serial('exitOffer tool reclaims stuck payment', async t => { - const offerId = 'bad-invitation-15'; // offer submitted on proposal upgrade-15 with an incorrect method name - const from = 'gov1'; - const before = await getBalances([GOV1ADDR], 'uist'); t.log('uist balance before:', before); - await $`node ./exitOffer.js --id ${offerId} --from ${from} `; + const offerId = 'bad-invitation-15'; // offer submitted on proposal upgrade-15 with an incorrect method name + await walletUtils.broadcastBridgeAction(GOV1ADDR, { + method: 'tryExitOffer', + offerId, + }); await waitForBlock(2); const after = await getBalances([GOV1ADDR], 'uist'); From 7acd16a0ba2307a96240c870f7625ef0d71b69f9 Mon Sep 17 00:00:00 2001 From: Jorge-Lopes Date: Tue, 5 Nov 2024 16:36:45 +0000 Subject: [PATCH 2/3] chore(a3p): replace waitForBlock with retryUntilCondition rel: https://github.com/Agoric/BytePitchPartnerEng/issues/10 --- .../proposals/z:acceptance/core-eval.test.js | 12 ++++++++++-- .../proposals/z:acceptance/localchain.test.js | 13 ++++++++++--- .../proposals/z:acceptance/valueVow.test.js | 11 +++++++++-- .../proposals/z:acceptance/wallet.test.js | 19 +++++++++++-------- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/core-eval.test.js b/a3p-integration/proposals/z:acceptance/core-eval.test.js index 0fc33270508..655bc9883a6 100644 --- a/a3p-integration/proposals/z:acceptance/core-eval.test.js +++ b/a3p-integration/proposals/z:acceptance/core-eval.test.js @@ -1,7 +1,10 @@ +/* global setTimeout */ + import test from 'ava'; import { readFile, writeFile } from 'node:fs/promises'; -import { agd, evalBundles, waitForBlock } from '@agoric/synthetic-chain'; +import { agd, evalBundles } from '@agoric/synthetic-chain'; +import { retryUntilCondition } from './test-lib/sync-tools.js'; const SUBMISSION_DIR = 'core-eval-test-submission'; @@ -45,7 +48,12 @@ test(`core eval works`, async t => { await evalBundles(SUBMISSION_DIR); - await waitForBlock(2); // enough time for core eval to execute ? + await retryUntilCondition( + async () => readPublished(nodePath), + value => value === nodeValue, + 'core eval not enforced yet', + { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, + ); t.is(await readPublished(nodePath), nodeValue); }); diff --git a/a3p-integration/proposals/z:acceptance/localchain.test.js b/a3p-integration/proposals/z:acceptance/localchain.test.js index b7145c64d6e..8a852265884 100644 --- a/a3p-integration/proposals/z:acceptance/localchain.test.js +++ b/a3p-integration/proposals/z:acceptance/localchain.test.js @@ -1,6 +1,8 @@ -import test from 'ava'; +/* global setTimeout */ -import { agd, evalBundles, waitForBlock } from '@agoric/synthetic-chain'; +import test from 'ava'; +import { agd, evalBundles } from '@agoric/synthetic-chain'; +import { retryUntilCondition } from './test-lib/sync-tools.js'; const SUBMISSION_DIR = 'localchaintest-submission'; @@ -27,7 +29,12 @@ test(`localchain passes tests`, async t => { const nodePath = 'test.localchain'; const nodeValue = JSON.stringify({ success: true }); - await waitForBlock(2); // enough time for core eval to execute ? + await retryUntilCondition( + async () => readPublished(nodePath), + value => value === nodeValue, + 'core eval not enforced yet', + { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, + ); t.is(await readPublished(nodePath), nodeValue); }); diff --git a/a3p-integration/proposals/z:acceptance/valueVow.test.js b/a3p-integration/proposals/z:acceptance/valueVow.test.js index 1193740b541..2f785eff9f9 100644 --- a/a3p-integration/proposals/z:acceptance/valueVow.test.js +++ b/a3p-integration/proposals/z:acceptance/valueVow.test.js @@ -9,12 +9,12 @@ import '@endo/init/debug.js'; import { evalBundles, getIncarnation, - waitForBlock, GOV1ADDR as GETTER, // not particular to governance, just a handy wallet GOV2ADDR as SETTER, // not particular to governance, just a handy wallet } from '@agoric/synthetic-chain'; import { makeWalletUtils } from './test-lib/wallet.js'; import { networkConfig } from './test-lib/index.js'; +import { retryUntilCondition } from './test-lib/sync-tools.js'; const START_VALUEVOW_DIR = 'start-valueVow'; const RESTART_VALUEVOW_DIR = 'restart-valueVow'; @@ -44,7 +44,14 @@ test('vow survives restart', async t => { }); t.log('confirm the value is not in offer results'); - await waitForBlock(2); + await retryUntilCondition( + async () => walletUtils.readLatestHead(`published.wallet.${GETTER}`), + getterStatus => + getterStatus.status.id === 'get-value' && + getterStatus.updated === 'offerStatus', + 'Offer get-value not succeeded', + { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, + ); { /** @type {any} */ const getterStatus = await walletUtils.readLatestHead( diff --git a/a3p-integration/proposals/z:acceptance/wallet.test.js b/a3p-integration/proposals/z:acceptance/wallet.test.js index f0dedd24503..c53c64fac9d 100644 --- a/a3p-integration/proposals/z:acceptance/wallet.test.js +++ b/a3p-integration/proposals/z:acceptance/wallet.test.js @@ -8,7 +8,6 @@ import { GOV1ADDR, GOV2ADDR, CHAINID, - waitForBlock, } from '@agoric/synthetic-chain'; import { execFileSync } from 'child_process'; import { @@ -16,6 +15,7 @@ import { getBalances, replaceTemplateValuesInFile, } from './test-lib/utils.js'; +import { retryUntilCondition } from './test-lib/sync-tools.js'; import { makeWalletUtils } from './test-lib/wallet.js'; import { networkConfig } from './test-lib/index.js'; @@ -52,21 +52,25 @@ test.serial(`send invitation via namesByAddress`, async t => { }); test.serial('exitOffer tool reclaims stuck payment', async t => { - const before = await getBalances([GOV1ADDR], 'uist'); - t.log('uist balance before:', before); + const istBalanceBefore = await getBalances([GOV1ADDR], 'uist'); const offerId = 'bad-invitation-15'; // offer submitted on proposal upgrade-15 with an incorrect method name await walletUtils.broadcastBridgeAction(GOV1ADDR, { method: 'tryExitOffer', offerId, }); - await waitForBlock(2); - const after = await getBalances([GOV1ADDR], 'uist'); - t.log('uist balance after:', after); + await retryUntilCondition( + async () => getBalances([GOV1ADDR], 'uist'), + istBalanceAfter => istBalanceAfter > istBalanceBefore, + 'tryExitOffer failed to reclaim stuck payment ', + { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, + ); + + const istBalanceAfter = await getBalances([GOV1ADDR], 'uist'); t.true( - after > before, + istBalanceAfter > istBalanceBefore, 'The IST balance should increase after reclaiming the stuck payment', ); }); @@ -108,7 +112,6 @@ test.serial(`ante handler sends fee only to vbank/reserve`, async t => { { chainId: CHAINID, from: GOV1ADDR, yes: true }, ); - await waitForBlock(); t.like(result, { code: 0 }); const [feeCollectorEndBalances, vbankReserveEndBalances] = await getBalances([ From ef55f0bb1279b3138e146cc427d45713a2b005fa Mon Sep 17 00:00:00 2001 From: Jorge-Lopes Date: Wed, 6 Nov 2024 11:08:41 +0000 Subject: [PATCH 3/3] chore(a3p): use the return value from retryUntilCondition rel: https://github.com/Agoric/BytePitchPartnerEng/issues/10 --- .../proposals/z:acceptance/core-eval.test.js | 6 +-- .../proposals/z:acceptance/localchain.test.js | 6 +-- .../proposals/z:acceptance/valueVow.test.js | 37 +++++++------------ .../proposals/z:acceptance/wallet.test.js | 6 +-- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/core-eval.test.js b/a3p-integration/proposals/z:acceptance/core-eval.test.js index 655bc9883a6..7b92e35a6cb 100644 --- a/a3p-integration/proposals/z:acceptance/core-eval.test.js +++ b/a3p-integration/proposals/z:acceptance/core-eval.test.js @@ -48,12 +48,12 @@ test(`core eval works`, async t => { await evalBundles(SUBMISSION_DIR); - await retryUntilCondition( + const actualValue = await retryUntilCondition( async () => readPublished(nodePath), value => value === nodeValue, - 'core eval not enforced yet', + 'core eval not processed yet', { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, ); - t.is(await readPublished(nodePath), nodeValue); + t.is(actualValue, nodeValue); }); diff --git a/a3p-integration/proposals/z:acceptance/localchain.test.js b/a3p-integration/proposals/z:acceptance/localchain.test.js index 8a852265884..3b8cd9c7e86 100644 --- a/a3p-integration/proposals/z:acceptance/localchain.test.js +++ b/a3p-integration/proposals/z:acceptance/localchain.test.js @@ -29,12 +29,12 @@ test(`localchain passes tests`, async t => { const nodePath = 'test.localchain'; const nodeValue = JSON.stringify({ success: true }); - await retryUntilCondition( + const actualValue = await retryUntilCondition( async () => readPublished(nodePath), value => value === nodeValue, - 'core eval not enforced yet', + 'core eval not processed yet', { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, ); - t.is(await readPublished(nodePath), nodeValue); + t.is(actualValue, nodeValue); }); diff --git a/a3p-integration/proposals/z:acceptance/valueVow.test.js b/a3p-integration/proposals/z:acceptance/valueVow.test.js index 2f785eff9f9..81cbae6de82 100644 --- a/a3p-integration/proposals/z:acceptance/valueVow.test.js +++ b/a3p-integration/proposals/z:acceptance/valueVow.test.js @@ -44,28 +44,21 @@ test('vow survives restart', async t => { }); t.log('confirm the value is not in offer results'); - await retryUntilCondition( + let getterStatus = await retryUntilCondition( async () => walletUtils.readLatestHead(`published.wallet.${GETTER}`), - getterStatus => - getterStatus.status.id === 'get-value' && - getterStatus.updated === 'offerStatus', + value => value.status.id === 'get-value' && value.updated === 'offerStatus', 'Offer get-value not succeeded', { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, ); - { - /** @type {any} */ - const getterStatus = await walletUtils.readLatestHead( - `published.wallet.${GETTER}`, - ); - console.log('current: ', inspect(getterStatus, { depth: 10 })); - t.like(getterStatus, { - status: { - id: 'get-value', - }, - updated: 'offerStatus', - }); - t.false('result' in getterStatus.status, 'no result yet'); - } + + console.log('current: ', inspect(getterStatus, { depth: 10 })); + t.like(getterStatus, { + status: { + id: 'get-value', + }, + updated: 'offerStatus', + }); + t.false('result' in getterStatus.status, 'no result yet'); t.log('restart valueVow'); await evalBundles(RESTART_VALUEVOW_DIR); @@ -89,11 +82,7 @@ test('vow survives restart', async t => { }); t.log('confirm the value is now in offer results'); - { - const getterStatus = await walletUtils.readLatestHead( - `published.wallet.${GETTER}`, - ); + getterStatus = await walletUtils.readLatestHead(`published.wallet.${GETTER}`); - t.like(getterStatus, { status: { result: offerArgs.value } }); - } + t.like(getterStatus, { status: { result: offerArgs.value } }); }); diff --git a/a3p-integration/proposals/z:acceptance/wallet.test.js b/a3p-integration/proposals/z:acceptance/wallet.test.js index c53c64fac9d..8d7375f385a 100644 --- a/a3p-integration/proposals/z:acceptance/wallet.test.js +++ b/a3p-integration/proposals/z:acceptance/wallet.test.js @@ -60,15 +60,13 @@ test.serial('exitOffer tool reclaims stuck payment', async t => { offerId, }); - await retryUntilCondition( + const istBalanceAfter = await retryUntilCondition( async () => getBalances([GOV1ADDR], 'uist'), - istBalanceAfter => istBalanceAfter > istBalanceBefore, + istBalance => istBalance > istBalanceBefore, 'tryExitOffer failed to reclaim stuck payment ', { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, ); - const istBalanceAfter = await getBalances([GOV1ADDR], 'uist'); - t.true( istBalanceAfter > istBalanceBefore, 'The IST balance should increase after reclaiming the stuck payment',