diff --git a/package.json b/package.json index d93c921cbd6..55fd148b9c1 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "type": "module", "devDependencies": { "@endo/eslint-config": "^0.5.1", - "@jessie.js/eslint-plugin": "^0.2.1", + "@jessie.js/eslint-plugin": "^0.3.0", "@types/node": "^16.7.10", "@typescript-eslint/parser": "^5.33.0", "ava": "^5.0.1", @@ -75,6 +75,7 @@ "**/espree/acorn": "^8.7.1", "**/puppeteer-core/node-fetch": "^2.6.5", "**/eslint/@babel/code-frame": "^7.12.11", - "**/eslint-config-react-app/eslint-plugin-jest": "^26.0.0" + "**/eslint-config-react-app/eslint-plugin-jest": "^26.0.0", + "**/@endo/eslint-config/@jessie.js/eslint-plugin": "^0.3.0" } } diff --git a/packages/SwingSet/package.json b/packages/SwingSet/package.json index 44b35244560..537c40f2ce0 100644 --- a/packages/SwingSet/package.json +++ b/packages/SwingSet/package.json @@ -48,6 +48,7 @@ "@endo/zip": "^0.2.28", "anylogger": "^0.21.0", "import-meta-resolve": "^1.1.1", + "jessie.js": "^0.3.2", "microtime": "^3.1.0", "semver": "^6.3.0" }, diff --git a/packages/SwingSet/src/vats/network/network.js b/packages/SwingSet/src/vats/network/network.js index 3007c1170b5..4e0da8d9b7a 100644 --- a/packages/SwingSet/src/vats/network/network.js +++ b/packages/SwingSet/src/vats/network/network.js @@ -3,6 +3,7 @@ import { E } from '@endo/eventual-send'; import { Far } from '@endo/marshal'; import { makePromiseKit } from '@endo/promise-kit'; import { assert, details as X, Fail } from '@agoric/assert'; +import { asyncGenerate } from 'jessie.js'; import { toBytes } from './bytes.js'; import '@agoric/store/exported.js'; @@ -235,18 +236,20 @@ export function makeNetworkProtocol(protocolHandler) { */ const bind = async localAddr => { // Check if we are underspecified (ends in slash) - if (localAddr.endsWith(ENDPOINT_SEPARATOR)) { - for (;;) { - // eslint-disable-next-line no-await-in-loop - const portID = await E(protocolHandler).generatePortID( - localAddr, - protocolHandler, - ); - const newAddr = `${localAddr}${portID}`; - if (!boundPorts.has(newAddr)) { - localAddr = newAddr; - break; - } + const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR); + const whileUnderspecified = asyncGenerate(() => ({ + done: !underspecified, + value: null, + })); + for await (const _ of whileUnderspecified) { + const portID = await E(protocolHandler).generatePortID( + localAddr, + protocolHandler, + ); + const newAddr = `${localAddr}${portID}`; + if (!boundPorts.has(newAddr)) { + localAddr = newAddr; + break; } } @@ -371,25 +374,24 @@ export function makeNetworkProtocol(protocolHandler) { bind, async inbound(listenAddr, remoteAddr) { let lastFailure = Error(`No listeners for ${listenAddr}`); - for (const listenPrefix of getPrefixes(listenAddr)) { + for await (const listenPrefix of getPrefixes(listenAddr)) { if (!listening.has(listenPrefix)) { - // eslint-disable-next-line no-continue continue; } const [port, listener] = listening.get(listenPrefix); let localAddr; - try { + await (async () => { // See if our protocol is willing to receive this connection. - // eslint-disable-next-line no-await-in-loop const localInstance = await E(protocolHandler) .onInstantiate(port, listenPrefix, remoteAddr, protocolHandler) .catch(rethrowUnlessMissing); localAddr = localInstance ? `${listenAddr}/${localInstance}` : listenAddr; - } catch (e) { + })().catch(e => { lastFailure = e; - // eslint-disable-next-line no-continue + }); + if (!localAddr) { continue; } // We have a legitimate inbound attempt. @@ -459,15 +461,19 @@ export function makeNetworkProtocol(protocolHandler) { : localAddr; let lastFailure; - try { + let accepted; + await (async () => { // Attempt the loopback connection. const attempt = await protocolImpl.inbound( remoteAddr, initialLocalAddr, ); - return attempt.accept({ handler: lchandler }); - } catch (e) { + accepted = await attempt.accept({ handler: lchandler }); + })().catch(e => { lastFailure = e; + }); + if (accepted) { + return accepted; } const { diff --git a/packages/agoric-cli/package.json b/packages/agoric-cli/package.json index 0fb8f60cb59..06708c53c89 100644 --- a/packages/agoric-cli/package.json +++ b/packages/agoric-cli/package.json @@ -59,6 +59,7 @@ "deterministic-json": "^1.0.5", "esm": "agoric-labs/esm#Agoric-built", "inquirer": "^8.2.2", + "jessie.js": "^0.3.2", "opener": "^1.5.2", "tmp": "^0.2.1", "ws": "^7.2.0" diff --git a/packages/agoric-cli/src/cosmos.js b/packages/agoric-cli/src/cosmos.js index 8751e2b69a9..09546354a00 100644 --- a/packages/agoric-cli/src/cosmos.js +++ b/packages/agoric-cli/src/cosmos.js @@ -84,11 +84,9 @@ export default async function cosmosMain(progname, rawArgs, powers, opts) { ); } - if (popts.pull) { - const exitStatus = await pspawn('docker', ['pull', IMAGE]); - if (exitStatus) { - return exitStatus; - } + const exitStatus = await (popts.pull && pspawn('docker', ['pull', IMAGE])); + if (exitStatus) { + return exitStatus; } return helper(rawArgs.slice(1)); diff --git a/packages/agoric-cli/src/deploy.js b/packages/agoric-cli/src/deploy.js index 25bce9ee9f1..a7475482c85 100644 --- a/packages/agoric-cli/src/deploy.js +++ b/packages/agoric-cli/src/deploy.js @@ -1,10 +1,8 @@ /* global process setTimeout setInterval clearInterval */ -/* eslint-disable no-await-in-loop */ import { E, makeCapTP } from '@endo/captp'; import { makePromiseKit } from '@endo/promise-kit'; import bundleSource from '@endo/bundle-source'; -import { makeCache } from '@agoric/cache'; import { makeLeaderFromRpcAddresses } from '@agoric/casting'; import { search as readContainingPackageDescriptor } from '@endo/compartment-mapper'; import url from 'url'; @@ -14,6 +12,7 @@ import inquirer from 'inquirer'; import createEsmRequire from 'esm'; import { createRequire } from 'module'; import { SigningStargateClient } from '@cosmjs/stargate'; +import { asyncGenerate } from 'jessie.js'; import { getAccessToken } from '@agoric/access-token'; @@ -50,7 +49,10 @@ export default async function deployMain(progname, rawArgs, powers, opts) { const console = anylogger('agoric:deploy'); const allowUnsafePlugins = opts.allowUnsafePlugins; - if (allowUnsafePlugins) { + const promptForUnsafePlugins = async () => { + if (!allowUnsafePlugins) { + return; + } const { yesReally } = await inquirer.prompt([ { name: 'yesReally', @@ -64,7 +66,8 @@ export default async function deployMain(progname, rawArgs, powers, opts) { ); process.exit(1); } - } + }; + await promptForUnsafePlugins(); const args = rawArgs.slice(1); const provide = opts.provide @@ -212,9 +215,9 @@ export default async function deployMain(progname, rawArgs, powers, opts) { const wsWebkey = `${wsurl}?accessToken=${encodeURIComponent(accessToken)}`; const ws = makeWebSocket(wsWebkey, { origin: wsurl.origin }); - ws.on('open', async () => { - connected = true; - try { + ws.on('open', () => { + const tryOnOpen = async () => { + connected = true; console.debug('Connected to CapTP!'); // Help disambiguate connections. const epoch = now(); @@ -245,7 +248,11 @@ export default async function deployMain(progname, rawArgs, powers, opts) { let lastUpdateCount; let stillLoading = [...need].sort(); progressDot = 'o'; - while (stillLoading.length) { + const untilNotLoading = asyncGenerate(() => ({ + done: !stillLoading.length, + value: stillLoading, + })); + for await (const _ of untilNotLoading) { // Wait for the notifier to report a new state. process.stdout.write(progressDot); console.debug('need:', stillLoading.join(', ')); @@ -298,29 +305,26 @@ export default async function deployMain(progname, rawArgs, powers, opts) { ); } - const cache = makeCache( - E(E(E.get(bootP).wallet).getBridge()).getCacheCoordinator(), - ); - let cachedLeader; const makeDefaultLeader = async leaderOptions => { - if (cachedLeader === undefined) { - const conn = await getDefaultConnection(); - const { type, rpcAddresses } = conn; - assert.equal( - type, - 'chain-cosmos-sdk', - X`${type} doesn't support casting followers`, - ); - cachedLeader = makeLeaderFromRpcAddresses( - rpcAddresses, - leaderOptions, - ); + if (cachedLeader) { + return cachedLeader; } + const conn = await getDefaultConnection(); + const { type, rpcAddresses } = conn; + assert.equal( + type, + 'chain-cosmos-sdk', + X`${type} doesn't support casting followers`, + ); + cachedLeader = makeLeaderFromRpcAddresses( + rpcAddresses, + leaderOptions, + ); return cachedLeader; }; - for (const arg of args) { + for await (const arg of args) { const moduleFile = path.resolve(process.cwd(), arg); const pathResolve = (...paths) => { const fileName = paths.pop(); @@ -344,8 +348,8 @@ export default async function deployMain(progname, rawArgs, powers, opts) { ); }; } else { - installUnsafePlugin = async (plugin, pluginOpts = undefined) => { - try { + installUnsafePlugin = (plugin, pluginOpts = undefined) => { + const tryInstallUnsafePlugin = async () => { const absPath = pathResolve(plugin); const pluginName = absPath.replace(PATH_SEP_RE, '_'); const pluginFile = path.resolve(pluginDir, pluginName); @@ -367,11 +371,12 @@ export { bootPlugin } from ${JSON.stringify(absPath)}; console.info(`Loading plugin ${JSON.stringify(pluginFile)}`); return E.get(E(pluginManager).load(pluginName, pluginOpts)) .pluginRoot; - } catch (e) { + }; + return tryInstallUnsafePlugin().catch(e => { throw Error( `Cannot install unsafe plugin: ${(e && e.stack) || e}`, ); - } + }); }; } @@ -401,88 +406,94 @@ export { bootPlugin } from ${JSON.stringify(absPath)}; ); const modulePath = pathResolve(moduleFile); - const mainNS = nativeEsm - ? await import(modulePath) - : esmRequire(modulePath); + let mainNS = await (nativeEsm && import(modulePath)); + if (!mainNS) { + mainNS = esmRequire(modulePath); + } const main = mainNS.default; if (typeof main !== 'function') { console.error( `${moduleFile} does not have an export default function main`, ); - } else { - await main(bootP, { - bundleSource: (file, options = undefined) => - bundleSource(pathResolve(file), options), - cache, - makeDefaultLeader, - publishBundle, - listConnections, - pathResolve, - installUnsafePlugin, - /** - * Recursively look up names in the context of the bootstrap - * promise, such as: - * - * ['agoricNames', 'oracleBrand', 'USD'] - * ['namesByAddress'] - * ['namesByAddress', 'agoric1...'] - * ['namesByAddress', 'agoric1...', 'depositFacet'] - * ['wallet', 'issuer', 'IST'] - * - * @param {...string[]} namePath - * @returns {Promise} - */ - lookup: (...namePath) => { - if (namePath.length === 1 && Array.isArray(namePath[0])) { - // Convert single array argument to a path. - namePath = namePath[0]; - } - if (namePath.length === 0) { - return bootP; - } - const [first, ...remaining] = namePath; + continue; + } + + await main(bootP, { + bundleSource: (file, options = undefined) => + bundleSource(pathResolve(file), options), + makeDefaultLeader, + publishBundle, + listConnections, + pathResolve, + installUnsafePlugin, + /** + * Recursively look up names in the context of the bootstrap + * promise, such as: + * + * ['agoricNames', 'oracleBrand', 'USD'] + * ['namesByAddress'] + * ['namesByAddress', 'agoric1...'] + * ['namesByAddress', 'agoric1...', 'depositFacet'] + * ['wallet', 'issuer', 'IST'] + * + * @param {...string[]} namePath + * @returns {Promise} + */ + lookup: (...namePath) => { + if (namePath.length === 1 && Array.isArray(namePath[0])) { + // Convert single array argument to a path. + namePath = namePath[0]; + } + if (namePath.length === 0) { + return bootP; + } + const [first, ...remaining] = namePath; + + // The first part of the name path is a property on bootP. + let nextValue = E.get(bootP)[first]; + if (remaining.length === 0) { + return nextValue; + } - // The first part of the name path is a property on bootP. - let nextValue = E.get(bootP)[first]; + // Compatibility for agoricdev-8; use `.get` for the next part. + // TODO: remove when agoricdev-9 is released. + if (first === 'scratch') { + const second = remaining.shift(); + const secondValue = E(nextValue).get(second); if (remaining.length === 0) { - return nextValue; + return secondValue; } - // Compatibility for agoricdev-8; use `.get` for the next part. - // TODO: remove when agoricdev-9 is released. - if (first === 'scratch') { - const second = remaining.shift(); - const secondValue = E(nextValue).get(second); - if (remaining.length === 0) { - return secondValue; - } - - // Fall through to the lookup below. - nextValue = secondValue; - } + // Fall through to the lookup below. + nextValue = secondValue; + } - // Any remaining paths go through the lookup method of the found - // object. - return E(nextValue).lookup(...remaining); - }, - host, - port, - args: opts.scriptArgs, - }); - } + // Any remaining paths go through the lookup method of the found + // object. + return E(nextValue).lookup(...remaining); + }, + host, + port, + args: opts.scriptArgs, + }); } - if (provide.length) { + const doneLoading = async () => { + if (!provide.length) { + return; + } console.debug('provide:', provide.join(', ')); await E(E.get(E.get(bootP).local).http).doneLoading(provide); - } + }; + await doneLoading(); console.debug('Done!'); ws.close(); exit.resolve(0); - } catch (e) { + }; + tryOnOpen().catch(e => { exit.reject(e); - } + }); }); ws.on('close', (_code, _reason) => { console.debug('connection closed'); diff --git a/packages/cache/package.json b/packages/cache/package.json index 7e617a519f6..3c5ec067ff6 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -24,7 +24,8 @@ "@agoric/vat-data": "^0.4.3", "@agoric/vats": "^0.13.0", "@endo/far": "^0.2.14", - "@endo/marshal": "^0.8.1" + "@endo/marshal": "^0.8.1", + "jessie.js": "^0.3.2" }, "devDependencies": { "@agoric/zoe": "^0.25.3", diff --git a/packages/cache/src/store.js b/packages/cache/src/store.js index 4a3929c1522..2c7cf60c03f 100644 --- a/packages/cache/src/store.js +++ b/packages/cache/src/store.js @@ -2,6 +2,7 @@ import { E, Far } from '@endo/far'; import { deeplyFulfilled, makeMarshal } from '@endo/marshal'; import { matches, makeScalarMapStore } from '@agoric/store'; import { makeScalarBigMapStore } from '@agoric/vat-data'; +import { asyncGenerate } from 'jessie.js'; import { withGroundState, makeState } from './state.js'; import './types.js'; @@ -76,8 +77,11 @@ const applyCacheTransaction = async ( // Loop until our updated state is fresh wrt our current state. basisState = stateStore.get(keyStr); - while (updatedState && updatedState.generation <= basisState.generation) { - // eslint-disable-next-line no-await-in-loop + const untilUpdateSynced = asyncGenerate(() => ({ + value: null, + done: !updatedState || updatedState.generation > basisState.generation, + })); + for await (const _ of untilUpdateSynced) { updatedState = await getUpdatedState(basisState); // AWAIT INTERLEAVING basisState = stateStore.get(keyStr); diff --git a/packages/cosmic-swingset/src/chain-main.js b/packages/cosmic-swingset/src/chain-main.js index e8e7ef52154..21bfd4f45b5 100644 --- a/packages/cosmic-swingset/src/chain-main.js +++ b/packages/cosmic-swingset/src/chain-main.js @@ -155,14 +155,11 @@ export default async function main(progname, args, { env, homedir, agcc }) { function registerPortHandler(portHandler) { lastPort += 1; const port = lastPort; - portHandlers[port] = async (...phArgs) => { - try { - return await portHandler(...phArgs); - } catch (e) { + portHandlers[port] = async (...phArgs) => + E.resolve(portHandler(...phArgs)).catch(e => { console.error('portHandler threw', e); throw e; - } - }; + }); return port; } @@ -305,13 +302,12 @@ export default async function main(progname, args, { env, homedir, agcc }) { ROLE: 'chain', bootMsg, }; - const vatconfig = new URL( - await importMetaResolve( - env.CHAIN_BOOTSTRAP_VAT_CONFIG || - argv.bootMsg.params.bootstrap_vat_config, - import.meta.url, - ), - ).pathname; + const vatHref = await importMetaResolve( + env.CHAIN_BOOTSTRAP_VAT_CONFIG || + argv.bootMsg.params.bootstrap_vat_config, + import.meta.url, + ); + const vatconfig = new URL(vatHref).pathname; const { metricsProvider } = getTelemetryProviders({ console, @@ -455,9 +451,7 @@ export default async function main(progname, args, { env, homedir, agcc }) { } // Ensure that initialization has completed. - if (!blockingSend) { - blockingSend = await launchAndInitializeSwingSet(action); - } + blockingSend = await (blockingSend || launchAndInitializeSwingSet(action)); if (action.type === AG_COSMOS_INIT) { // console.error('got AG_COSMOS_INIT', action); diff --git a/packages/eslint-config/eslint-config.json b/packages/eslint-config/eslint-config.json index 9e8cfc2b3b4..7221f8848a3 100644 --- a/packages/eslint-config/eslint-config.json +++ b/packages/eslint-config/eslint-config.json @@ -2,5 +2,8 @@ "extends": [ "@endo", "plugin:@jessie.js/recommended" - ] + ], + "rules": { + "no-continue": "off" + } } diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 2015913309b..ab676cabd1e 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -26,7 +26,7 @@ ], "peerDependencies": { "@endo/eslint-config": "^0.5.1", - "@jessie.js/eslint-plugin": "^0.2.1", + "@jessie.js/eslint-plugin": "^0.3.0", "@typescript-eslint/parser": "^5.33.0", "eslint": "^7.32.0", "eslint-config-airbnb-base": "^14.0.0", diff --git a/packages/telemetry/src/ingest-slog-entrypoint.js b/packages/telemetry/src/ingest-slog-entrypoint.js index f900aebe307..711c9977c21 100755 --- a/packages/telemetry/src/ingest-slog-entrypoint.js +++ b/packages/telemetry/src/ingest-slog-entrypoint.js @@ -81,6 +81,16 @@ async function run() { console.log(`parsing`, slogFileName); let update = false; + const maybeUpdateStats = async now => { + if ( + now - lastTime <= ELAPSED_MS_TO_FLUSH && + lineCount % LINE_COUNT_TO_FLUSH !== 0 + ) { + return; + } + lastTime = now; + await stats(update); + }; for await (const line of lines) { lineCount += 1; const obj = harden(JSON.parse(line)); @@ -90,17 +100,9 @@ async function run() { } let now = Date.now(); - if ( - now - lastTime > ELAPSED_MS_TO_FLUSH || - lineCount % LINE_COUNT_TO_FLUSH === 0 - ) { - lastTime = now; - // eslint-disable-next-line @jessie.js/no-nested-await - await stats(update); - } + await maybeUpdateStats(now); if (!update) { - // eslint-disable-next-line no-continue continue; } @@ -110,7 +112,6 @@ async function run() { const delayMS = PROCESSING_PERIOD - (now - startOfLastPeriod); maybeWait = new Promise(resolve => setTimeout(resolve, delayMS)); } - // eslint-disable-next-line @jessie.js/no-nested-await await maybeWait; now = Date.now(); diff --git a/yarn.lock b/yarn.lock index 258f27cd3d2..5fb16bbe109 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1334,7 +1334,7 @@ resolved "https://registry.yarnpkg.com/@endo/eventual-send/-/eventual-send-0.16.8.tgz#d0084bd13c2034cbc8394968c49b8648f7224e5f" integrity sha512-bxWzRrcrCS44m0N0lCGA7BQoBFSfKkzwpFGGdZNVbUbT7+b9fzOV58sdSzzxxGnl6UwG24KzX71CNNA3PxJI9w== -"@endo/far@^0.2.14": +"@endo/far@^0.2.14", "@endo/far@^0.2.3": version "0.2.14" resolved "https://registry.yarnpkg.com/@endo/far/-/far-0.2.14.tgz#fe85854bed9d6affbc723ab0f0c28ccbb422ae27" integrity sha512-9fYkYgpoLU6bsYJhlXrMwlDNMi3hXZd/AHhxk9GvqOctEGGEMfUWkW/ZxQYmo9eyIE3b+VhktJDJz2hmoQPS6Q== @@ -1579,10 +1579,10 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jessie.js/eslint-plugin@^0.2.0", "@jessie.js/eslint-plugin@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@jessie.js/eslint-plugin/-/eslint-plugin-0.2.1.tgz#857f778963fd95179649be4fa601271bb5800303" - integrity sha512-SgQoa2O0uOKf6j2WMXFZc1KE+pOCzacV/uoyq0e4A/jm7Tja8/TJR+TlCPpYnx/YCMH23SCYC0Uz7T+5Qw6Byg== +"@jessie.js/eslint-plugin@^0.2.0", "@jessie.js/eslint-plugin@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@jessie.js/eslint-plugin/-/eslint-plugin-0.3.0.tgz#5b084355f3cba288422d5ecf794e1290baeddd45" + integrity sha512-7v7lZCxP9t6eMu95+3J9nuxLHCLjBZgJ+1cICR9gAga7OGWlC1AdncDO9H2B+Tn0ATfd+w/G9w1yqJq7n53kMA== dependencies: requireindex "~1.1.0" @@ -8212,6 +8212,13 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jessie.js@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/jessie.js/-/jessie.js-0.3.2.tgz#68159e1097f86fa56a3136668313ea38e96fc9f2" + integrity sha512-Gpa2iZhl/hPZGT3HWEURfYIV5kFjeHZmQLZuRF6A5czuezmEOlnDYH3w7rohaaEWwxJiXprUWj4r6lh1JIkXAA== + dependencies: + "@endo/far" "^0.2.3" + jest-worker@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"