Skip to content

Commit

Permalink
fixup! feat(orchestration): make timerUtils resumable
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Jun 21, 2024
1 parent 99f7653 commit 18889a2
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 71 deletions.
2 changes: 2 additions & 0 deletions packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { deeplyFulfilled } from '@endo/marshal';
import { M } from '@endo/patterns';
import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js';
import { makeChainHub } from '../exos/chain-hub.js';
import { makeTimestampHelper } from '../exos/time.js';

/**
* @import {NameHub} from '@agoric/vats';
Expand Down Expand Up @@ -49,6 +50,7 @@ export const start = async (zcf, privateArgs, baggage) => {
privateArgs.timerService,
vowTools,
makeChainHub(privateArgs.agoricNames),
makeTimestampHelper(privateArgs.timerService),
);

// ----------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ import {
CoinShape,
DelegationShape,
} from '../typeGuards.js';
import { maxClockSkew, tryDecodeResponse } from '../utils/cosmos.js';
import {
maxClockSkew,
tryDecodeResponse,
dateInSeconds,
} from '../utils/cosmos.js';
import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js';
import { dateInSeconds } from '../utils/time.js';

/**
* @import {AmountArg, IcaAccount, ChainAddress, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount, OrchestrationAccountI, DenomArg} from '../types.js';
Expand Down
15 changes: 7 additions & 8 deletions packages/orchestration/src/exos/local-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import {
ChainAmountShape,
IBCTransferOptionsShape,
} from '../typeGuards.js';
import { maxClockSkew } from '../utils/cosmos.js';
import { maxClockSkew, dateInSeconds } from '../utils/cosmos.js';
import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js';
import { dateInSeconds, makeTimestampHelper } from '../utils/time.js';

/**
* @import {LocalChainAccount} from '@agoric/vats/src/localchain.js';
Expand All @@ -27,6 +26,7 @@ import { dateInSeconds, makeTimestampHelper } from '../utils/time.js';
* @import {PromiseVow, VowTools} from '@agoric/vow';
* @import {TypedJson} from '@agoric/cosmic-proto';
* @import {ChainHub} from './chain-hub.js';
* @import {TimestampHelper} from './time.js';
*/

const trace = makeTracer('LOA');
Expand Down Expand Up @@ -66,6 +66,7 @@ const PUBLIC_TOPICS = {
* @param {Remote<TimerService>} timerService
* @param {VowTools} vowTools
* @param {ChainHub} chainHub
* @param {TimestampHelper} timestampHelper
*/
export const prepareLocalOrchestrationAccountKit = (
zone,
Expand All @@ -74,11 +75,10 @@ export const prepareLocalOrchestrationAccountKit = (
timerService,
{ watch, when, allVows },
chainHub,
) => {
const timestampHelper = makeTimestampHelper(timerService);

timestampHelper,
) =>
/** Make an object wrapping an LCA with Zoe interfaces. */
const makeLocalOrchestrationAccountKit = zone.exoClassKit(
zone.exoClassKit(
'Local Orchestration Account Kit',
{
holder: HolderI,
Expand Down Expand Up @@ -423,7 +423,6 @@ export const prepareLocalOrchestrationAccountKit = (
},
},
);
return makeLocalOrchestrationAccountKit;
};

/** @typedef {ReturnType<typeof prepareLocalOrchestrationAccountKit>} MakeLocalOrchestrationAccountKit */
/** @typedef {ReturnType<MakeLocalOrchestrationAccountKit>} LocalOrchestrationAccountKit */
99 changes: 56 additions & 43 deletions packages/orchestration/src/exos/time.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,76 @@
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 { TimeMath } from '@agoric/time';
import { M } from '@endo/patterns';

/**
* @import {RelativeTimeRecord, TimerBrand, TimerService} from '@agoric/time';
* @import {Remote} from '@agoric/internal';
* @import {Remote} from '@agoric/internal';*
* @import {RelativeTimeRecord, TimerBrand, TimerService, TimestampRecord} from '@agoric/time';
* @import {Vow} from '@agoric/vow';
* @import {Zone} from '@agoric/zone';
*/

export const SECONDS_PER_MINUTE = 60n;
export const NANOSECONDS_PER_SECOND = 1_000_000_000n;

/**
* XXX should this be durable? resumable?
*
* @param {Remote<TimerService>} timer
* @param {Remote<TimerService>} timerService
* @param {Zone} [zone]
*/
export function makeTimestampHelper(timer) {

export const makeTimestampHelper = (timerService, zone = makeHeapZone()) => {
/** @type {TimerBrand | undefined} */
let brandCache;
const getBrand = async () => {
if (brandCache) return brandCache;
brandCache = await E(timer).getTimerBrand();
return brandCache;
return watch(E(timerService).getTimerBrand(), {
onFulfilled: timerBrand => {
return timerBrand;
},
});
};

return harden({
/**
* XXX do this need to be resumable / use Vows?
*
* Takes the current time from ChainTimerService and adds a relative time to
* determine a timeout timestamp in nanoseconds. Useful for
* {@link MsgTransfer.timeoutTimestamp}.
*
* @param {RelativeTimeRecord} [relativeTime] defaults to 5 minutes
* @returns {Promise<bigint>} Timeout timestamp in absolute nanoseconds
* since unix epoch
*/
async getTimeoutTimestampNS(relativeTime) {
const currentTime = await E(timer).getCurrentTimestamp();
const timeout =
relativeTime ||
TimeMath.coerceRelativeTimeRecord(
SECONDS_PER_MINUTE * 5n,
await getBrand(),
return zone.exo(
'Timestamp Helper',
M.interface('TimeStampHelperI', {
getTimeoutTimestampNS: M.call()
.optional(RelativeTimeRecordShape)
.returns(VowShape),
}),
{
/**
* Takes the current time from ChainTimerService and adds a relative time
* to determine a timeout timestamp in nanoseconds. Useful for
* {@link MsgTransfer.timeoutTimestamp}.
*
* @param {RelativeTimeRecord} [relativeTime] defaults to 5 minutes
* @returns {Vow<bigint>} Timeout timestamp in absolute nanoseconds since
* unix epoch
*/
getTimeoutTimestampNS(relativeTime) {
return watch(
allVows([E(timerService).getCurrentTimestamp(), getBrand()]),
{
/** @param {[TimestampRecord, TimerBrand]} r */
onFulfilled([currentTime, timerBrand]) {
const timeout =
relativeTime ||
TimeMath.coerceRelativeTimeRecord(
SECONDS_PER_MINUTE * 5n,
timerBrand,
);
return (
TimeMath.addAbsRel(currentTime, timeout).absValue *
NANOSECONDS_PER_SECOND
);
},
},
);
return (
TimeMath.addAbsRel(currentTime, timeout).absValue *
NANOSECONDS_PER_SECOND
);
},
},
});
}
);
};

/** @typedef {Awaited<ReturnType<typeof makeTimestampHelper>>} TimestampHelper */

/**
* Convert a Date from a Cosmos message, which has millisecond precision, to a
* BigInt for number of seconds since epoch, for use in a timer.
*
* @param {Date} date
* @returns {bigint}
*/
export const dateInSeconds = date => BigInt(Math.floor(date.getTime() / 1000));
9 changes: 9 additions & 0 deletions packages/orchestration/src/utils/cosmos.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,12 @@ export const tryDecodeResponse = (ackStr, fromProtoMsg) => {
throw assert.error(`bad response: ${ackStr}`, undefined, { cause });
}
};

/**
* Convert a Date from a Cosmos message, which has millisecond precision, to a
* BigInt for number of seconds since epoch, for use in a timer.
*
* @param {Date} date
* @returns {bigint}
*/
export const dateInSeconds = date => BigInt(Math.floor(date.getTime() / 1000));
3 changes: 3 additions & 0 deletions packages/orchestration/src/utils/start-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { makeChainHub } from '../exos/chain-hub.js';
import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js';
import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js';
import { prepareLocalChainFacade } from '../exos/local-chain-facade.js';
import { makeTimestampHelper } from '../exos/time.js';

/**
* @import {PromiseKit} from '@endo/promise-kit'
Expand Down Expand Up @@ -47,6 +48,7 @@ export const provideOrchestration = (
const zone = makeDurableZone(baggage);

const chainHub = makeChainHub(remotePowers.agoricNames);
const timestampHelper = makeTimestampHelper(remotePowers.timerService);

const vowTools = prepareVowTools(zone.subZone('vows'));

Expand All @@ -58,6 +60,7 @@ export const provideOrchestration = (
remotePowers.timerService,
vowTools,
chainHub,
timestampHelper,
);

const asyncFlowTools = prepareAsyncFlowTools(zone.subZone('asyncFlow'), {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { Far } from '@endo/far';
import { prepareLocalOrchestrationAccountKit } from '../../src/exos/local-orchestration-account.js';
import { ChainAddress } from '../../src/orchestration-api.js';
import { makeChainHub } from '../../src/exos/chain-hub.js';
import { NANOSECONDS_PER_SECOND } from '../../src/utils/time.js';
import {
makeTimestampHelper,
NANOSECONDS_PER_SECOND,
} from '../../src/exos/time.js';
import { commonSetup } from '../supports.js';

test('deposit, withdraw', async t => {
Expand All @@ -34,6 +37,7 @@ test('deposit, withdraw', async t => {
timer,
vowTools,
makeChainHub(bootstrap.agoricNames),
makeTimestampHelper(timer),
);

t.log('request account from vat-localchain');
Expand Down Expand Up @@ -105,6 +109,7 @@ test('delegate, undelegate', async t => {
timer,
vowTools,
makeChainHub(bootstrap.agoricNames),
makeTimestampHelper(timer),
);

t.log('request account from vat-localchain');
Expand Down Expand Up @@ -157,6 +162,7 @@ test('transfer', async t => {
timer,
vowTools,
makeChainHub(bootstrap.agoricNames),
makeTimestampHelper(timer),
);

t.log('request account from vat-localchain');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
/* 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 { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js';
import { TimeMath } from '@agoric/time';
import {
dateInSeconds,
makeTimestampHelper,
NANOSECONDS_PER_SECOND,
SECONDS_PER_MINUTE,
} from '../../src/utils/time.js';
} from '../../src/exos/time.js';

test('makeTimestampHelper - getCurrentTimestamp', async t => {
const timer = buildZoeManualTimer(t.log);
const timerBrand = timer.getTimerBrand();
t.is(timer.getCurrentTimestamp().absValue, 0n, 'current time is 0n');

const { getTimeoutTimestampNS } = makeTimestampHelper(timer);
await null;
const timestampHelper = makeTimestampHelper(timer);
t.is(
await getTimeoutTimestampNS(),
await V.when(timestampHelper.getTimeoutTimestampNS()),
5n * SECONDS_PER_MINUTE * NANOSECONDS_PER_SECOND,
'default timestamp is 5 minutes from current time, in nanoseconds',
);

t.is(
await getTimeoutTimestampNS(
TimeMath.coerceRelativeTimeRecord(1n, timerBrand),
await V.when(
timestampHelper.getTimeoutTimestampNS(
TimeMath.coerceRelativeTimeRecord(1n, timerBrand),
),
),
1n * NANOSECONDS_PER_SECOND,
'timestamp is 1 second since unix epoch, in nanoseconds',
Expand All @@ -32,18 +34,12 @@ test('makeTimestampHelper - getCurrentTimestamp', async t => {
// advance timer by 3 seconds
await timer.tickN(3);
t.is(
await getTimeoutTimestampNS(
TimeMath.coerceRelativeTimeRecord(1n, timerBrand),
await V.when(
timestampHelper.getTimeoutTimestampNS(
TimeMath.coerceRelativeTimeRecord(1n, timerBrand),
),
),
(1n + 3n) * NANOSECONDS_PER_SECOND,
'timestamp is 4 seconds since unix epoch, in nanoseconds',
);
});

test('dateInSeconds', t => {
t.is(dateInSeconds(new Date(1)), 0n);
t.is(dateInSeconds(new Date(999)), 0n);
t.is(dateInSeconds(new Date(1000)), 1n);

t.is(dateInSeconds(new Date('2025-12-17T12:23:45Z')), 1765974225n);
});
10 changes: 10 additions & 0 deletions packages/orchestration/test/utils/cosmos.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import { dateInSeconds } from '../../src/utils/cosmos.js';

test('dateInSeconds', t => {
t.is(dateInSeconds(new Date(1)), 0n);
t.is(dateInSeconds(new Date(999)), 0n);
t.is(dateInSeconds(new Date(1000)), 1n);

t.is(dateInSeconds(new Date('2025-12-17T12:23:45Z')), 1765974225n);
});

0 comments on commit 18889a2

Please sign in to comment.