diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index 1651a3a58d4..3e5a4071f1a 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -6,6 +6,7 @@ import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/record import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; +import { makeHeapZone } from '@agoric/zone'; import { prepareVowTools, V } from '@agoric/vow/vat.js'; import { E } from '@endo/far'; import { deeplyFulfilled } from '@endo/marshal'; @@ -36,21 +37,23 @@ const trace = makeTracer('StakeBld'); */ export const start = async (zcf, privateArgs, baggage) => { const zone = makeDurableZone(baggage); + const { agoricNames, marshaller, timerService } = privateArgs; - const { makeRecorderKit } = prepareRecorderKitMakers( - baggage, - privateArgs.marshaller, - ); + const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const vowTools = prepareVowTools(zone.subZone('vows')); + const timeHelper = makeTimeHelper(makeHeapZone(), { + timerService, + vowTools, + }); const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( zone, makeRecorderKit, zcf, - privateArgs.timerService, + timerService, vowTools, - makeChainHub(privateArgs.agoricNames), - makeTimeHelper(privateArgs.timerService), + makeChainHub(agoricNames), + timeHelper, ); // ---------------- diff --git a/packages/orchestration/src/exos/time-helper.js b/packages/orchestration/src/exos/time-helper.js index e2bb3694138..9786af5f597 100644 --- a/packages/orchestration/src/exos/time-helper.js +++ b/packages/orchestration/src/exos/time-helper.js @@ -1,14 +1,12 @@ import { RelativeTimeRecordShape, TimeMath } from '@agoric/time'; import { VowShape } from '@agoric/vow'; -import { watch, allVows } from '@agoric/vow/vat.js'; -import { makeHeapZone } from '@agoric/zone'; -import { E } from '@endo/far'; +import { E, Far } from '@endo/far'; import { M } from '@endo/patterns'; /** * @import {Remote} from '@agoric/internal';* * @import {RelativeTimeRecord, TimerBrand, TimerService, TimestampRecord} from '@agoric/time'; - * @import {Vow} from '@agoric/vow'; + * @import {Vow, VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; */ @@ -16,31 +14,41 @@ export const SECONDS_PER_MINUTE = 60n; export const NANOSECONDS_PER_SECOND = 1_000_000_000n; /** - * @param {Remote} timerService - * @param {Zone} [zone] + * @param {Zone} zone + * @param {{ timerService: Remote; vowTools: VowTools }} powers */ - -export const makeTimeHelper = (timerService, zone = makeHeapZone()) => { - /** @type {TimerBrand | undefined} */ - let brandCache; - const getBrand = () => { - if (brandCache) return brandCache; - return watch(E(timerService).getTimerBrand(), { - onFulfilled: timerBrand => { - brandCache = timerBrand; - return timerBrand; - }, - }); - }; - - return zone.exo( +export const makeTimeHelper = ( + zone, + { timerService, vowTools: { watch, allVows } }, +) => { + const timeHelper = zone.exoClass( 'Time Helper', M.interface('TimeHelperI', { getTimeoutTimestampNS: M.call() .optional(RelativeTimeRecordShape) .returns(VowShape), + getBrand: M.call().returns(VowShape), }), + () => + /** @type {{ brandCache: TimerBrand | undefined }} */ ({ + brandCache: undefined, + }), { + /** @returns {Vow} */ + getBrand() { + // XXX this is a common use case that should have a helper like `provideSingleton` + if (this.state.brandCache) return watch(this.state.brandCache); + return watch( + E(timerService).getTimerBrand(), + Far('BrandWatcher', { + /** @param {TimerBrand} timerBrand */ + onFulfilled: timerBrand => { + this.state.brandCache = timerBrand; + return timerBrand; + }, + }), + ); + }, /** * Takes the current time from ChainTimerService and adds a relative time * to determine a timeout timestamp in nanoseconds. Useful for @@ -52,8 +60,11 @@ export const makeTimeHelper = (timerService, zone = makeHeapZone()) => { */ getTimeoutTimestampNS(relativeTime) { return watch( - allVows([E(timerService).getCurrentTimestamp(), getBrand()]), - { + allVows([ + E(timerService).getCurrentTimestamp(), + this.self.getBrand(), + ]), + Far('TimestampWatcher', { /** @param {[TimestampRecord, TimerBrand]} results */ onFulfilled([currentTime, timerBrand]) { const timeout = @@ -67,11 +78,13 @@ export const makeTimeHelper = (timerService, zone = makeHeapZone()) => { NANOSECONDS_PER_SECOND ); }, - }, + }), ); }, }, ); + return timeHelper(); }; +harden(makeTimeHelper); -/** @typedef {Awaited>} TimeHelper */ +/** @typedef {ReturnType} TimeHelper */ diff --git a/packages/orchestration/src/utils/start-helper.js b/packages/orchestration/src/utils/start-helper.js index 5bf1c6a8e28..337f594bc85 100644 --- a/packages/orchestration/src/utils/start-helper.js +++ b/packages/orchestration/src/utils/start-helper.js @@ -2,6 +2,7 @@ import { prepareAsyncFlowTools } from '@agoric/async-flow'; import { prepareVowTools } from '@agoric/vow'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; +import { makeHeapZone } from '@agoric/zone'; import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; import { makeOrchestrationFacade } from '../facade.js'; import { makeChainHub } from '../exos/chain-hub.js'; @@ -46,11 +47,15 @@ export const provideOrchestration = ( marshaller, ) => { const zone = makeDurableZone(baggage); + const vowTools = prepareVowTools(zone.subZone('vows')); - const chainHub = makeChainHub(remotePowers.agoricNames); - const timeHelper = makeTimeHelper(remotePowers.timerService); + const { agoricNames, timerService } = remotePowers; - const vowTools = prepareVowTools(zone.subZone('vows')); + const chainHub = makeChainHub(agoricNames); + const timeHelper = makeTimeHelper(makeHeapZone(), { + timerService, + vowTools, + }); const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( diff --git a/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts index 09e75f88cfa..8dfd4819e04 100644 --- a/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts @@ -4,6 +4,7 @@ import { AmountMath } from '@agoric/ertp'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { V as E } from '@agoric/vow/vat.js'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; +import { makeHeapZone } from '@agoric/zone'; import { Far } from '@endo/far'; import { prepareLocalOrchestrationAccountKit } from '../../src/exos/local-orchestration-account.js'; import { ChainAddress } from '../../src/orchestration-api.js'; @@ -37,7 +38,7 @@ test('deposit, withdraw', async t => { timer, vowTools, makeChainHub(bootstrap.agoricNames), - makeTimeHelper(timer), + makeTimeHelper(makeHeapZone(), { timerService: timer, vowTools }), ); t.log('request account from vat-localchain'); @@ -109,7 +110,7 @@ test('delegate, undelegate', async t => { timer, vowTools, makeChainHub(bootstrap.agoricNames), - makeTimeHelper(timer), + makeTimeHelper(makeHeapZone(), { timerService: timer, vowTools }), ); t.log('request account from vat-localchain'); @@ -162,7 +163,7 @@ test('transfer', async t => { timer, vowTools, makeChainHub(bootstrap.agoricNames), - makeTimeHelper(timer), + makeTimeHelper(makeHeapZone(), { timerService: timer, vowTools }), ); t.log('request account from vat-localchain'); diff --git a/packages/orchestration/test/exos/time-helper.test.ts b/packages/orchestration/test/exos/time-helper.test.ts index e761b5ce3ed..691cb054de9 100644 --- a/packages/orchestration/test/exos/time-helper.test.ts +++ b/packages/orchestration/test/exos/time-helper.test.ts @@ -1,8 +1,9 @@ /* eslint-disable @jessie.js/safe-await-separator */ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { V } from '@agoric/vow/vat.js'; +import { prepareVowTools, V } from '@agoric/vow/vat.js'; import { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js'; import { TimeMath } from '@agoric/time'; +import { makeHeapZone } from '@agoric/zone'; import { makeTimeHelper, NANOSECONDS_PER_SECOND, @@ -11,15 +12,24 @@ import { test('makeTimeHelper - getCurrentTimestamp', async t => { const timer = buildZoeManualTimer(t.log); + const vowTools = prepareVowTools(makeHeapZone()); const timerBrand = timer.getTimerBrand(); t.is(timer.getCurrentTimestamp().absValue, 0n, 'current time is 0n'); - const timeHelper = makeTimeHelper(timer); + const timeHelper = makeTimeHelper(makeHeapZone(), { + timerService: timer, + vowTools, + }); t.is( await V.when(timeHelper.getTimeoutTimestampNS()), 5n * SECONDS_PER_MINUTE * NANOSECONDS_PER_SECOND, 'default timestamp is 5 minutes from current time, in nanoseconds', ); + t.is( + await V.when(timeHelper.getBrand()), + timerBrand, + 'brand retrieved cache equals timerBrand', + ); t.is( await V.when(