From 27709b4d75f1c74d59fcd11e29499d828c5b60b5 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 29 Jul 2024 16:51:57 -0400 Subject: [PATCH 1/2] feat(examples): logging progress --- .../src/examples/send-anywhere.contract.js | 15 ++++++++++--- .../src/examples/send-anywhere.flows.js | 20 +++++++++++++----- .../snapshots/send-anywhere.test.ts.md | 2 ++ .../snapshots/send-anywhere.test.ts.snap | Bin 1215 -> 1239 bytes 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/orchestration/src/examples/send-anywhere.contract.js b/packages/orchestration/src/examples/send-anywhere.contract.js index 4fc4690fa2e..b0ecf667fc7 100644 --- a/packages/orchestration/src/examples/send-anywhere.contract.js +++ b/packages/orchestration/src/examples/send-anywhere.contract.js @@ -1,12 +1,15 @@ import { makeSharedStateRecord } from '@agoric/async-flow'; + import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; +import { E } from '@endo/far'; import { M } from '@endo/patterns'; -import { withOrchestration } from '../utils/start-helper.js'; -import * as flows from './send-anywhere.flows.js'; import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js'; import { AnyNatAmountShape } from '../typeGuards.js'; +import { withOrchestration } from '../utils/start-helper.js'; +import * as flows from './send-anywhere.flows.js'; /** + * @import {Vow} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; * @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js'; */ @@ -33,7 +36,7 @@ const contract = async ( zcf, privateArgs, zone, - { chainHub, orchestrateAll, zoeTools }, + { chainHub, orchestrateAll, vowTools, zoeTools }, ) => { const contractState = makeSharedStateRecord( /** @type {{ account: OrchestrationAccount | undefined }} */ { @@ -43,9 +46,15 @@ const contract = async ( const creatorFacet = prepareChainHubAdmin(zone, chainHub); + // UNTIL https://github.com/Agoric/agoric-sdk/issues/9066 + const logNode = E(privateArgs.storageNode).makeChildNode('log'); + /** @type {(msg: string) => Vow} */ + const log = msg => vowTools.watch(E(logNode).setValue(msg)); + // orchestrate uses the names on orchestrationFns to do a "prepare" of the associated behavior const orchFns = orchestrateAll(flows, { contractState, + log, zoeTools, }); diff --git a/packages/orchestration/src/examples/send-anywhere.flows.js b/packages/orchestration/src/examples/send-anywhere.flows.js index a72d5db77fd..74ab64b5f03 100644 --- a/packages/orchestration/src/examples/send-anywhere.flows.js +++ b/packages/orchestration/src/examples/send-anywhere.flows.js @@ -3,7 +3,8 @@ import { makeError, q } from '@endo/errors'; import { M, mustMatch } from '@endo/patterns'; /** - * @import {GuestInterface} from '@agoric/async-flow'; + * @import {GuestInterface, GuestOf} from '@agoric/async-flow'; + * @import {Vow} from '@agoric/vow'; * @import {ZoeTools} from '../utils/zoe-tools.js'; * @import {Orchestrator, LocalAccountMethods, OrchestrationAccountI, OrchestrationFlow} from '../types.js'; */ @@ -19,12 +20,13 @@ const { entries } = Object; * @param {object} ctx * @param {{ localAccount?: OrchestrationAccountI & LocalAccountMethods }} ctx.contractState * @param {GuestInterface} ctx.zoeTools + * @param {GuestOf<(msg: string) => Vow>} ctx.log * @param {ZCFSeat} seat * @param {{ chainName: string; destAddr: string }} offerArgs */ export const sendIt = async ( orch, - { contractState, zoeTools: { localTransfer, withdrawToSeat } }, + { contractState, log, zoeTools: { localTransfer, withdrawToSeat } }, seat, offerArgs, ) => { @@ -33,25 +35,30 @@ export const sendIt = async ( // NOTE the proposal shape ensures that the `give` is a single asset const { give } = seat.getProposal(); const [[_kw, amt]] = entries(give); + void log(`sending {${amt.value}} from ${chainName} to ${destAddr}`); const agoric = await orch.getChain('agoric'); const assets = await agoric.getVBankAssetInfo(); + void log(`got info for denoms: ${assets.map(a => a.denom).join(', ')}`); const { denom } = NonNullish( assets.find(a => a.brand === amt.brand), `${amt.brand} not registered in vbank`, ); - const chain = await orch.getChain(chainName); + // FIXME racy if (!contractState.localAccount) { - const agoricChain = await orch.getChain('agoric'); - contractState.localAccount = await agoricChain.makeAccount(); + contractState.localAccount = await agoric.makeAccount(); } + const chain = await orch.getChain(chainName); const info = await chain.getChainInfo(); const { chainId } = info; assert(typeof chainId === 'string', 'bad chainId'); + void log(`got info for chain: ${chainName} ${chainId}`); await localTransfer(seat, contractState.localAccount, give); + void log(`completed transfer to localAccount`); + try { await contractState.localAccount.transfer( { @@ -61,13 +68,16 @@ export const sendIt = async ( }, { denom, value: amt.value }, ); + void log(`completed transfer to ${destAddr}`); } catch (e) { await withdrawToSeat(contractState.localAccount, seat, give); const errorMsg = `IBC Transfer failed ${q(e)}`; + void log(`ERROR: ${errorMsg}`); seat.exit(errorMsg); throw makeError(errorMsg); } seat.exit(); + void log(`transfer complete, seat exited`); }; harden(sendIt); diff --git a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md index 5f4dbdca9a3..6d091f7bb4f 100644 --- a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md @@ -44,6 +44,8 @@ Generated by [AVA](https://avajs.dev). 0: { contractState_kindHandle: 'Alleged: kind', contractState_singleton: 'Alleged: contractState', + log_kindHandle: 'Alleged: kind', + log_singleton: 'Alleged: log', zoeTools: { localTransfer_kindHandle: 'Alleged: kind', localTransfer_singleton: 'Alleged: localTransfer', diff --git a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap index bab47226e8b51cb8fa66f49fa275be951a16c45b..0149838132a32474b67377e6ba9f0795e280ebfb 100644 GIT binary patch literal 1239 zcmV;|1StDKRzVS50plMHqg@_S)-@UE57UlO})*;s6H(CnUrrO>q-7X+o2< zaG@ISPV8;gGuG}HQ%>y#QK<(cE(rBPM1BA#4i!g`5C_DmAOVL`Aue1vA%PI`I&1IF z5-S^@EI%JJ^M1@b{&cV93TxlLbHJn{UDj^VcAK`D<8#{cJ3{(*4s4rB`c~D+qJZIt z=tOS=sbfEkjnll*4uCbJ|V#{bpUu_IA#bf3wvxH|e?*u}VkPQNa zaFP1AxYcyUAb{>iAl>Rvhp+Zq0rD`02rfNp$pA4k6T}dn)nUH$sB{Dm;5Q<0?j14k zhf%ED&H(RcfUh!ukp(VgfxB7YVHT+6fXy7x&jBCjfCo7xs+o9HbyD~<2mF--Ue1xSn*Uh?e${|y^1#hJaF7Rn$}6(6Dl7h-$1s`LEdYlF;I{(stPU_8 zxUB=9=!!*Mwa9&;17GUEKRWPQQK8QzDyOVuePP+|I(#|IU)rLXSLD9K+b)xWhlI0Z zb)2dA%wBMYyL1FkyMv`>p9jKSKJaL-ccS`@IV>g}62~|L zX_*Vw-Ib2(pFpaQhN8CYdGr>^hTp2y>KF?LW=@%}aa5a%7N(;5RJ1r1EvZvK^QZ*; zUQ(ugWva=U#M1#~AYTS9mw|g_#cU?A<_~4y7nL@fcwvyBnx3poswwX?Zm-GzEu^Q{ zF0AZ$l>2+k8{Q?%J5855E$b@t`>s4~JthniSKh){uX4jvD-AN~pp%_QC)w2saHRr# zP*I|6B%+*AA5b*K6V4>$vq*kQjN4++Wn2c&>ddzl;Cpoo<}rzW95Es<1&`rH12}I0 zHw@s=0Dd%pvZ?r06MpLSoDcsX1P`Os7jImh$mbJdag3fpuIQ-T*Gsy#4p zWXJaCU`K2-n!2m!75S9sPn^kI+G6gzOJQOgM}Nv2bWbv`;Y;et{{XXANqo5v002-M BR*L`t literal 1215 zcmV;w1VH;iRzVSIci3MI8Rd_O92D^`l8>(gv{L1cwSvNQg_C;wEa+gghGc zgvPrQdzH&!hLU16We*ogpL&Xs!!~t~THK4j97E^D`FyG`57@j31J9U=YOhqg^6y05e3d6Mds~gISW0@z+|d24aHbL8_P`nOTwr(dy)x zOMQ>FT-Mm^w_L~XG_E-E%>#$qE0o(V3u)$(>#{bpUu_IA#baiRS;DiKcLHIBkaYrt zaDnuOK!(+!4qxfF0_0%~5nOuIk^xf5Ob}CeR)_i0qtX#PfZvF~xwpi? zA7-(9D+9cn0lvxrW)`@d1@2^lhgqPO12%F%KL>o610Lj*tY+d_HAwz(4){9e${~Ibl`>#9O}T2x*{v7vceM`!(w7L58TTGzvY4F4S*TI zEd%(-P%P@IMee=oE*^h|n;?c_e_>hpUEw!2qRhsUWr;qQjTPaZ z>@JFVzPKhV>KspqPFCD^d`xM->TK>7z;1VoN~X$QEe)kpNbk&(ZW=;sNVXSUy8u* zMdh|HPc=D{csig2=q2Dv3AkHQ%w`g6{!jvbR%x?|7X}Hs>FM&Mn{=OXdsY5#BR#u% zae2q1+}~&3@GN28X{OX^SzFBSyYjUCm@r5jc?)B`$_=Ad24pfpCyU4+*_ASIwG6yh zR8-PYEkVRJ(HE$d2vN!H(EwH1(X#EAlB1k~ovOw8h-F d7sHe{j{YV$=)PoL!P{{b7NPT@-r007%iSZV+O From b9c469bb76361220f845ce297ff7dd2c178facfb Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 16 Jul 2024 14:16:02 -0700 Subject: [PATCH 2/2] test: failing test of orchestration upgrade --- .../orchestration/contract-upgrade.test.ts | 87 +++++++++++ .../scripts/testing/fix-buggy-sendAnywhere.js | 142 ++++++++++++++++++ .../testing/start-buggy-sendAnywhere.js | 142 ++++++++++++++++++ .../src/examples/send-anywhere.contract.js | 3 +- 4 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 packages/boot/test/orchestration/contract-upgrade.test.ts create mode 100644 packages/builders/scripts/testing/fix-buggy-sendAnywhere.js create mode 100644 packages/builders/scripts/testing/start-buggy-sendAnywhere.js diff --git a/packages/boot/test/orchestration/contract-upgrade.test.ts b/packages/boot/test/orchestration/contract-upgrade.test.ts new file mode 100644 index 00000000000..c8dcc03f3c9 --- /dev/null +++ b/packages/boot/test/orchestration/contract-upgrade.test.ts @@ -0,0 +1,87 @@ +/** @file Bootstrap test of restarting contracts using orchestration */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { TestFn } from 'ava'; + +import { + makeWalletFactoryContext, + type WalletFactoryTestContext, +} from '../bootstrapTests/walletFactory.js'; + +const test: TestFn = anyTest; +test.before(async t => { + t.context = await makeWalletFactoryContext( + t, + '@agoric/vm-config/decentral-itest-orchestration-config.json', + ); +}); +test.after.always(t => t.context.shutdown?.()); + +/** + * This test core-evals a buggy installation of the sendAnywhere contract by + * giving it a faulty `agoricNames` service with a lookup() function which + * returns a promise that never resolves. + * + * Because the send-anywhere flow requires a lookup(), it waits forever. This + * gives us a point at which we can upgrade the vat with a working agoricNames + * and see that the flow continues from that point. (The lookup call is not made + * directly in a flow, but instead from a host API which uses the retriable + * helper. As such it tests both the idempotent retry mechanism of retriable on + * upgrades, and the ability to resume an async-flow for which a host vow + * settles after an upgrade.) + */ +test.failing('resume', async t => { + const { walletFactoryDriver, buildProposal, evalProposal, storage } = + t.context; + + const { IST } = t.context.agoricNamesRemotes.brand; + + t.log('start sendAnywhere'); + await evalProposal( + buildProposal( + '@agoric/builders/scripts/testing/start-buggy-sendAnywhere.js', + ), + ); + + t.log('making offer'); + const wallet = await walletFactoryDriver.provideSmartWallet('agoric1test'); + // no money in wallet to actually send + const zero = { brand: IST, value: 0n }; + // send because it won't resolve + await wallet.sendOffer({ + id: 'send-somewhere', + invitationSpec: { + source: 'agoricContract', + instancePath: ['sendAnywhere'], + callPipe: [['makeSendInvitation']], + }, + proposal: { + // @ts-expect-error XXX BoardRemote + give: { Send: zero }, + }, + offerArgs: { destAddr: 'cosmos1whatever', chainName: 'cosmoshub' }, + }); + + // XXX golden test + const getLogged = () => + JSON.parse(storage.data.get('published.sendAnywhere.log')!).values; + + // This log shows the flow started, but didn't get past the name lookup + t.deepEqual(getLogged(), ['sending {0} from cosmoshub to cosmos1whatever']); + + t.log('upgrade sendAnywhere with fix'); + await evalProposal( + buildProposal('@agoric/builders/scripts/testing/fix-buggy-sendAnywhere.js'), + ); + + // FIXME https://github.com/Agoric/agoric-sdk/issues/9303 + // This doesn't yet get past 'sending' + t.deepEqual(getLogged(), [ + 'sending {0} from cosmoshub to cosmos1whatever', + 'got info for denoms: ibc/toyatom, ibc/toyusdc, ubld, uist', + 'got info for chain: cosmoshub cosmoshub-4', + 'completed transfer to localAccount', + // But does not get to a complete transaction without mocking the IBC transfer acknowledgementPacket + // TODO file a ticket for providing that and also fixing it in restart-contracts's .failing test + // 'transfer complete, seat exited', + ]); +}); diff --git a/packages/builders/scripts/testing/fix-buggy-sendAnywhere.js b/packages/builders/scripts/testing/fix-buggy-sendAnywhere.js new file mode 100644 index 00000000000..fdb31f11d44 --- /dev/null +++ b/packages/builders/scripts/testing/fix-buggy-sendAnywhere.js @@ -0,0 +1,142 @@ +/** + * @file This is for use in tests in a3p-integration + * Unlike most builder scripts, this one includes the proposal exports as well. + */ +import { + deeplyFulfilledObject, + makeTracer, + NonNullish, +} from '@agoric/internal'; +import { E, Far } from '@endo/far'; + +/// +/** + * @import {Installation, Instance} from '@agoric/zoe/src/zoeService/utils.js'; + */ + +const trace = makeTracer('FixBuggySA', true); + +/** + * @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js'; + */ + +/** + * @param {BootstrapPowers & { + * instance: { + * consume: { + * sendAnywhere: Instance; + * }; + * }; + * }} powers + * @param {...any} rest + */ +export const fixSendAnywhere = async ( + { + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + contractKits, + cosmosInterchainService, + localchain, + }, + instance: instances, + }, + { options: { sendAnywhereRef } }, +) => { + trace(fixSendAnywhere.name); + + const saInstance = await instances.consume.sendAnywhere; + trace('saInstance', saInstance); + const saKit = await E(contractKits).get(saInstance); + + const marshaller = await E(board).getReadonlyMarshaller(); + + // This apparently pointless wrapper is to maintain structural parity + // with the buggy core-eval's wrapper to make lookup() hang. + const agoricNamesResolves = Far('agoricNames that resolves', { + lookup: async (...args) => { + return E(agoricNames).lookup(...args); + }, + }); + + const privateArgs = await deeplyFulfilledObject( + harden({ + agoricNames: agoricNamesResolves, + localchain, + marshaller, + orchestrationService: cosmosInterchainService, + storageNode: E(NonNullish(await chainStorage)).makeChildNode( + 'sendAnywhere', + ), + timerService: chainTimerService, + }), + ); + + trace('upgrading...'); + await E(saKit.adminFacet).upgradeContract( + sendAnywhereRef.bundleID, + privateArgs, + ); + + trace('done'); +}; +harden(fixSendAnywhere); + +export const getManifestForValueVow = ({ restoreRef }, { sendAnywhereRef }) => { + console.log('sendAnywhereRef', sendAnywhereRef); + return { + manifest: { + [fixSendAnywhere.name]: { + consume: { + agoricNames: true, + board: true, + chainStorage: true, + chainTimerService: true, + cosmosInterchainService: true, + localchain: true, + + contractKits: true, + }, + installation: { + consume: { sendAnywhere: true }, + }, + instance: { + consume: { sendAnywhere: true }, + }, + }, + }, + installations: { + sendAnywhere: restoreRef(sendAnywhereRef), + }, + options: { + sendAnywhereRef, + }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }) => + harden({ + // Somewhat unorthodox, source the exports from this builder module + sourceSpec: '@agoric/builders/scripts/testing/fix-buggy-sendAnywhere.js', + getManifestCall: [ + 'getManifestForValueVow', + { + sendAnywhereRef: publishRef( + install( + '@agoric/orchestration/src/examples/send-anywhere.contract.js', + ), + ), + }, + ], + }); + +export default async (homeP, endowments) => { + // import dynamically so the module can work in CoreEval environment + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(fixSendAnywhere.name, defaultProposalBuilder); +}; diff --git a/packages/builders/scripts/testing/start-buggy-sendAnywhere.js b/packages/builders/scripts/testing/start-buggy-sendAnywhere.js new file mode 100644 index 00000000000..7ba83f8c950 --- /dev/null +++ b/packages/builders/scripts/testing/start-buggy-sendAnywhere.js @@ -0,0 +1,142 @@ +/** + * @file This is for use in tests in a3p-integration + * Unlike most builder scripts, this one includes the proposal exports as well. + */ +import { + deeplyFulfilledObject, + makeTracer, + NonNullish, +} from '@agoric/internal'; +import { E, Far } from '@endo/far'; + +/// +/** + * @import {Installation} from '@agoric/zoe/src/zoeService/utils.js'; + */ + +const trace = makeTracer('StartBuggySA', true); + +/** + * @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js'; + */ + +/** + * @param {BootstrapPowers & { + * installation: { + * consume: { + * sendAnywhere: Installation; + * }; + * }; + * }} powers + */ +export const startSendAnywhere = async ({ + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + cosmosInterchainService, + localchain, + startUpgradable, + }, + installation: { + consume: { sendAnywhere }, + }, + instance: { + // @ts-expect-error unknown instance + produce: { sendAnywhere: produceInstance }, + }, +}) => { + trace(startSendAnywhere.name); + + const marshaller = await E(board).getReadonlyMarshaller(); + + const privateArgs = await deeplyFulfilledObject( + harden({ + agoricNames, + localchain, + marshaller, + orchestrationService: cosmosInterchainService, + storageNode: E(NonNullish(await chainStorage)).makeChildNode( + 'sendAnywhere', + ), + timerService: chainTimerService, + }), + ); + + /** @type {import('@agoric/vats').NameHub} */ + // @ts-expect-error intentional fake + const agoricNamesHangs = Far('agoricNames that hangs', { + lookup: async () => { + trace('agoricNames.lookup being called that will never resolve'); + // BUG: this never resolves + return new Promise(() => {}); + }, + }); + + const { instance } = await E(startUpgradable)({ + label: 'sendAnywhere', + installation: sendAnywhere, + privateArgs: { + ...privateArgs, + agoricNames: agoricNamesHangs, + }, + }); + produceInstance.resolve(instance); + trace('done'); +}; +harden(startSendAnywhere); + +export const getManifestForValueVow = ({ restoreRef }, { sendAnywhereRef }) => { + trace('sendAnywhereRef', sendAnywhereRef); + return { + manifest: { + [startSendAnywhere.name]: { + consume: { + agoricNames: true, + board: true, + chainStorage: true, + chainTimerService: true, + cosmosInterchainService: true, + localchain: true, + + startUpgradable: true, + }, + installation: { + consume: { sendAnywhere: true }, + }, + instance: { + produce: { sendAnywhere: true }, + }, + }, + }, + installations: { + sendAnywhere: restoreRef(sendAnywhereRef), + }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }) => + harden({ + // Somewhat unorthodox, source the exports from this builder module + sourceSpec: '@agoric/builders/scripts/testing/start-buggy-sendAnywhere.js', + getManifestCall: [ + 'getManifestForValueVow', + { + sendAnywhereRef: publishRef( + install( + '@agoric/orchestration/src/examples/send-anywhere.contract.js', + ), + ), + }, + ], + }); + +export default async (homeP, endowments) => { + // import dynamically so the module can work in CoreEval environment + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(startSendAnywhere.name, defaultProposalBuilder); +}; diff --git a/packages/orchestration/src/examples/send-anywhere.contract.js b/packages/orchestration/src/examples/send-anywhere.contract.js index b0ecf667fc7..f555817d7dd 100644 --- a/packages/orchestration/src/examples/send-anywhere.contract.js +++ b/packages/orchestration/src/examples/send-anywhere.contract.js @@ -32,7 +32,7 @@ harden(SingleNatAmountRecord); * @param {Zone} zone * @param {OrchestrationTools} tools */ -const contract = async ( +export const contract = async ( zcf, privateArgs, zone, @@ -77,6 +77,7 @@ const contract = async ( return { publicFacet, creatorFacet }; }; +harden(contract); export const start = withOrchestration(contract); harden(start);