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

refactor(vats): Remove makeBridgeTargetKit from prepareTransferTools #4

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5a03adc
Add new Vtransfer module for notifications (#8624)
mergify[bot] Jun 8, 2024
27cc9b8
test(async-flow): demonstrate #9465 (#9474)
erights Jun 9, 2024
09fe78c
chore(types): s/ProposalBuilder/CoreEvalBuilder
turadg Jun 7, 2024
9349c1d
chore: s/writeCoreProposal/writeCoreEval
turadg Jun 7, 2024
03c6382
chore: s/writeCoreProposal/writeCoreEval
turadg Jun 7, 2024
f6b46c5
refactor: remove Proposal terminology
turadg Jun 7, 2024
5c93775
refactor: rename writeCoreEvalParts for clarity
turadg Jun 7, 2024
16ca97e
chore(types): define CoreEvalDescriptor
turadg Jun 8, 2024
593361d
refactor: naming
turadg Jun 8, 2024
721b875
clarify CoreEval vs proposal (#9468)
mergify[bot] Jun 9, 2024
f89659b
chore: IBCConnectionInfoShape typeGuard
dckc Jun 6, 2024
34e87c0
chore: deposit() guard was missing optional pattern
dckc Jun 6, 2024
78fbc6d
feat: chainHub
dckc Jun 6, 2024
98be3ab
chore(local-chain-account-kit): use chainHub
dckc Jun 6, 2024
3a5a16a
chore(facade): move registerChain to chainHub
dckc Jun 6, 2024
aa328a2
chore: get timer brand from service on-demand
dckc Jun 7, 2024
81e3c99
chore: use chainHub in orch example contracts
dckc Jun 7, 2024
376956e
chore(local-chain-account-kit): TimerService is Remote<>
dckc Jun 7, 2024
71a3f7c
test(orch supports): for sendAnywhere
dckc Jun 6, 2024
8d90251
feat: sendAnywhere contract, test
dckc Jun 6, 2024
96c19f5
feat: generic chain with sendAnywhere demo contract (#9460)
mergify[bot] Jun 10, 2024
e193e66
feat(async-flow): error on guest E use (#9443)
erights Jun 10, 2024
8d11989
refactor(vats): Rename vtransfer TransferInterceptorKit facets by the…
gibson042 Jun 7, 2024
0960242
refactor(vtransfer): transfer interceptor updates (#9470)
mergify[bot] Jun 10, 2024
9b2bfbb
refactor(vats): Remove makeBridgeTargetKit from prepareTransferTools
gibson042 Jun 7, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) =>
harden({
sourceSpec: './init-proposal.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) =>
harden({
sourceSpec: './upgrade-proposal.js',
Expand Down
77 changes: 67 additions & 10 deletions packages/async-flow/src/replay-membrane.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/* eslint-disable no-use-before-define */
import { Fail, b, q } from '@endo/errors';
import { Fail, X, b, makeError, q } from '@endo/errors';
import { Far, Remotable, getInterfaceOf } from '@endo/pass-style';
import { E } from '@endo/eventual-send';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { makePromiseKit } from '@endo/promise-kit';
import { makeEquate } from './equate.js';
import { makeConvertKit } from './convert.js';

const { fromEntries, defineProperties } = Object;
/**
* @import {PromiseKit} from '@endo/promise-kit'
*/

const { fromEntries, defineProperties, assign } = Object;

/**
* @param {LogStore} log
Expand All @@ -31,6 +34,8 @@ export const makeReplayMembrane = (

let stopped = false;

const Panic = (template, ...args) => panic(makeError(X(template, ...args)));

// ////////////// Host or Interpreter to Guest ///////////////////////////////

/**
Expand Down Expand Up @@ -196,9 +201,61 @@ export const makeReplayMembrane = (
default: {
// @ts-expect-error TS correctly knows this case would be outside
// the type. But that's what we want to check.
throw Fail`unexpected outcome kind ${q(outcome.kind)}`;
throw Panic`unexpected outcome kind ${q(outcome.kind)}`;
}
}
};

// //////////////// Eventual Send ////////////////////////////////////////////

const guestHandler = harden({
applyMethod(guestTarget, optVerb, guestArgs, guestReturnedP) {
if (optVerb === undefined) {
throw Panic`guest eventual call not yet supported: ${guestTarget}(${b(guestArgs)}) -> ${b(guestReturnedP)}`;
} else {
throw Panic`guest eventual send not yet supported: ${guestTarget}.${b(optVerb)}(${b(guestArgs)}) -> ${b(guestReturnedP)}`;
}
},
applyFunction(guestTarget, guestArgs, guestReturnedP) {
return guestHandler.applyMethod(
guestTarget,
undefined,
guestArgs,
guestReturnedP,
);
},
get(guestTarget, prop, guestReturnedP) {
throw Panic`guest eventual get not yet supported: ${guestTarget}.${b(prop)} -> ${b(guestReturnedP)}`;
},
});

const makeGuestPresence = (iface, methodEntries) => {
let guestPresence;
void new HandledPromise((_res, _rej, resolveWithPresence) => {
guestPresence = resolveWithPresence(guestHandler);
}); // no unfulfilledHandler
if (typeof guestPresence !== 'object') {
throw Fail`presence expected to be object ${guestPresence}`;
}
assign(guestPresence, fromEntries(methodEntries));
const result = Remotable(iface, undefined, guestPresence);
result === guestPresence ||
Fail`Remotable expected to make presence in place: ${guestPresence} vs ${result}`;
return result;
};

/**
* @returns {PromiseKit<any>}
*/
const makeGuestPromiseKit = () => {
let resolve;
let reject;
const promise = new HandledPromise((res, rej, _resPres) => {
resolve = res;
reject = rej;
}, guestHandler);
// @ts-expect-error TS cannot infer that it is a PromiseKit
return harden({ promise, resolve, reject });
};

// //////////////// Converters ///////////////////////////////////////////////
Expand Down Expand Up @@ -246,9 +303,7 @@ export const makeReplayMembrane = (
name,
makeGuestMethod(name),
]);
// TODO in order to support E *well*,
// use HandledPromise to make gRem a remote presence for hRem
gRem = Remotable(guestIface, undefined, fromEntries(guestMethods));
gRem = makeGuestPresence(guestIface, guestMethods);
}
// See note at the top of the function to see why clearing the `hRem`
// variable is safe, and what invariant the above code needs to maintain so
Expand All @@ -258,10 +313,12 @@ export const makeReplayMembrane = (
};
harden(makeGuestForHostRemotable);

/**
* @param {Vow} hVow
* @returns {Promise}
*/
const makeGuestForHostVow = hVow => {
// TODO in order to support E *well*,
// use HandledPromise to make `promise` a handled promise for hVow
const { promise, resolve, reject } = makePromiseKit();
const { promise, resolve, reject } = makeGuestPromiseKit();
guestPromiseMap.set(promise, harden({ resolve, reject }));

watchWake(hVow);
Expand Down
201 changes: 201 additions & 0 deletions packages/async-flow/test/async-flow-early-completion.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// eslint-disable-next-line import/order
import {
test,
getBaggage,
annihilate,
nextLife,
} from './prepare-test-env-ava.js';

import { Fail } from '@endo/errors';
import { passStyleOf } from '@endo/pass-style';
import { makeCopyMap } from '@endo/patterns';
import { makePromiseKit } from '@endo/promise-kit';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import { isVow } from '@agoric/vow/src/vow-utils.js';
import { prepareVowTools } from '@agoric/vow';
import { makeDurableZone } from '@agoric/zone/durable.js';

import { prepareAsyncFlowTools } from '../src/async-flow.js';

/**
* @import {AsyncFlow} from '../src/async-flow.js'
*/

/**
* @param {Zone} zone
* @param {number} [k]
*/
const prepareOrchestra = (zone, k = 1) =>
zone.exoClass(
'Orchestra',
undefined,
(factor, vow, resolver) => ({ factor, vow, resolver }),
{
scale(n) {
const { state } = this;
return k * state.factor * n;
},
vow() {
const { state } = this;
return state.vow;
},
resolve(x) {
const { state } = this;
state.resolver.resolve(x);
},
},
);

/** @typedef {ReturnType<ReturnType<prepareOrchestra>>} Orchestra */

const firstLogLen = 7;

/**
* @param {any} t
* @param {Zone} zone
*/
const testFirstPlay = async (t, zone) => {
t.log('firstPlay started');
const vowTools = prepareVowTools(zone);
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
vowTools,
});
const makeOrchestra = prepareOrchestra(zone);
const { makeVowKit } = vowTools;

const { vow: v1, resolver: r1 } = zone.makeOnce('v1', () => makeVowKit());
const { vow: v2, resolver: r2 } = zone.makeOnce('v2', () => makeVowKit());
const { vow: v3, resolver: _r3 } = zone.makeOnce('v3', () => makeVowKit());
const hOrch7 = zone.makeOnce('hOrch7', () => makeOrchestra(7, v2, r2));

// purposely violate rule that guestMethod is closed.
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();

const { guestMethod } = {
async guestMethod(gOrch7, g1, _p3) {
t.log(' firstPlay about to await g1');
t.is(await g1, 'x');
const p2 = gOrch7.vow();
const prod = gOrch7.scale(3);
t.is(prod, 21);

let gErr;
try {
gOrch7.scale(9n);
} catch (e) {
gErr = e;
}
t.is(gErr.name, 'TypeError');

resolveStep(true);
t.log(' firstPlay to hang awaiting p2');
// awaiting a promise that won't be resolved until next incarnation
await p2;
t.fail('must not reach here in first incarnation');
},
};

const wrapperFunc = asyncFlow(zone, 'AsyncFlow1', guestMethod);

const outcomeV = zone.makeOnce('outcomeV', () => wrapperFunc(hOrch7, v1, v3));

t.true(isVow(outcomeV));
r1.resolve('x');

const flow = zone.makeOnce('flow', () =>
adminAsyncFlow.getFlowForOutcomeVow(outcomeV),
);
t.is(passStyleOf(flow), 'remotable');

await promiseStep;

const logDump = flow.dump();
t.is(logDump.length, firstLogLen);
t.deepEqual(logDump, [
['doFulfill', v1, 'x'],
['checkCall', hOrch7, 'vow', [], 1],
['doReturn', 1, v2],
['checkCall', hOrch7, 'scale', [3], 3],
['doReturn', 3, 21],
['checkCall', hOrch7, 'scale', [9n], 5],
[
'doThrow',
5,
TypeError('Cannot mix BigInt and other types, use explicit conversions'),
],
]);
t.log('firstPlay done');
};

/**
* Test from bug https://github.com/Agoric/agoric-sdk/issues/9465
*
* @param {any} t
* @param {Zone} zone
*/
const testBadShortReplay = async (t, zone) => {
t.log('badShortReplay started');
const vowTools = prepareVowTools(zone);
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
vowTools,
});
prepareOrchestra(zone);
const { when } = vowTools;

// purposely violate rule that guestMethod is closed.
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();

const { guestMethod } = {
async guestMethod(_gOrch7, _g1, _p3) {
t.log(' badReplay return early');
resolveStep(true);
return 'bad';
},
};

// `asyncFlow` can be used simply to re-prepare the guest function
// by ignoring the returned wrapper function. If the wrapper function is
// invoked, that would be a *new* activation with a new outcome and
// flow, and would have nothing to do with the existing one.
asyncFlow(zone, 'AsyncFlow1', guestMethod);

const outcomeV = /** @type {Vow} */ (
zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
);
const flow = /** @type {AsyncFlow} */ (
zone.makeOnce('flow', () => Fail`need flow`)
);
const flow1 = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
t.is(flow, flow1);
t.is(passStyleOf(flow), 'remotable');

await promiseStep;

const replayProblem = flow.getOptFatalProblem();
t.log(' badShortReplay failures', replayProblem);
t.true(replayProblem instanceof Error);

const outcome = when(outcomeV);
await eventLoopIteration();

t.is(await Promise.race([outcome, Promise.resolve('good')]), 'good');

t.deepEqual(
adminAsyncFlow.getFailures(),
makeCopyMap([[flow, replayProblem]]),
);

t.log('badShortReplay done');
};

test.serial.failing('test durable async-flow early completion', async t => {
annihilate();
const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
await testFirstPlay(t, zone1);

await eventLoopIteration();

nextLife();
const zone2a = makeDurableZone(getBaggage(), 'durableRoot');
await testBadShortReplay(t, zone2a);
});
Loading