From ece3ec493cf238687dbe6b3bcc2f4f722050f9ef Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 11 Jun 2024 17:41:57 -0400 Subject: [PATCH] feat: make vat-localchain resumable Co-authored-by: Ikenna Omekam --- .../src/exos/local-chain-account-kit.js | 2 +- .../test/exos/local-chain-account-kit.test.ts | 3 +- packages/vats/src/localchain.js | 114 +++++++++++++++--- .../vats/src/proposals/localchain-test.js | 2 +- packages/vats/src/vat-localchain.js | 7 +- packages/vats/test/localchain.test.js | 6 +- 6 files changed, 109 insertions(+), 25 deletions(-) diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index 950c37745dc..d728ea0a357 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -6,7 +6,7 @@ import { makeTracer } from '@agoric/internal'; import { M } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; -import { E } from '@endo/far'; +import { V as E } from '@agoric/vow/vat.js'; import { AmountArgShape, ChainAddressShape, diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-chain-account-kit.test.ts index a1e9f1a4a28..b5ec9ee8780 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-chain-account-kit.test.ts @@ -2,7 +2,8 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { AmountMath } from '@agoric/ertp'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; -import { E, Far } from '@endo/far'; +import { V as E } from '@agoric/vow/vat.js'; +import { Far } from '@endo/far'; import { commonSetup } from '../supports.js'; import { prepareLocalChainAccountKit } from '../../src/exos/local-chain-account-kit.js'; import { ChainAddress } from '../../src/orchestration-api.js'; diff --git a/packages/vats/src/localchain.js b/packages/vats/src/localchain.js index 6988eb1e2a0..2c962a1d80b 100644 --- a/packages/vats/src/localchain.js +++ b/packages/vats/src/localchain.js @@ -2,11 +2,15 @@ import { E } from '@endo/far'; import { M } from '@endo/patterns'; import { AmountShape, BrandShape, PaymentShape } from '@agoric/ertp'; +import { Shape as NetworkShape } from '@agoric/network'; +import { V } from '@agoric/vow/vat.js'; const { Fail } = assert; /** * @import {TypedJson, ResponseTo, JsonSafe} from '@agoric/cosmic-proto'; + * @import {PromiseVow, VowTools, Watcher} from '@agoric/vow'; + * @import {TargetApp, TargetRegistration} from './bridge-target.js'; * @import {BankManager, Bank} from './vat-bank.js'; * @import {ScopedBridgeManager} from './types.js'; */ @@ -27,10 +31,47 @@ const { Fail } = assert; * }} LocalChainPowers */ +/** + * @param {import('@agoric/zone').Zone} zone + */ +const prepareDepositPaymentHandler = zone => + zone.exoClass( + 'DepositPaymentHandler', + M.interface('DepositPaymentHandlerI', { + onFulfilled: M.call(BrandShape) + .optional({ + bank: M.remotable('Bank Power'), + payment: PaymentShape, + optAmountShape: M.or(M.undefined(), AmountShape), + }) + .returns(M.promise()), + }), + () => ({}), + /** @type {Watcher, Amount<'nat'>>} */ + { + /** + * @param {Brand<'nat'>} brand + * @param {{ + * bank: Bank; + * payment: Payment<'nat'>; + * optAmountShape: Amount<'nat'>; + * }} ctx + */ + onFulfilled(brand, { bank, payment, optAmountShape }) { + const purse = E(bank).getPurse(brand); + return E(purse).deposit(payment, optAmountShape); + }, + }, + ); + +/** @typedef {ReturnType} DepositPaymentHandler */ + export const LocalChainAccountI = M.interface('LocalChainAccount', { getAddress: M.callWhen().returns(M.string()), getBalance: M.callWhen(BrandShape).returns(AmountShape), - deposit: M.callWhen(PaymentShape).optional(M.pattern()).returns(AmountShape), + deposit: M.callWhen(PaymentShape) + .optional(M.pattern()) + .returns(NetworkShape.Vow$(AmountShape)), withdraw: M.callWhen(AmountShape).returns(PaymentShape), executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), monitorTransfers: M.callWhen(M.remotable('TransferTap')).returns( @@ -38,22 +79,38 @@ export const LocalChainAccountI = M.interface('LocalChainAccount', { ), }); -/** @param {import('@agoric/base-zone').Zone} zone */ -const prepareLocalChainAccount = zone => - zone.exoClass( +/** + * @param {import('@agoric/base-zone').Zone} zone + * @param {DepositPaymentHandler} makeDepositPaymentHandler + * @param {VowTools} vowTools + */ +const prepareLocalChainAccount = ( + zone, + makeDepositPaymentHandler, + { watch }, +) => { + const depositPaymentHandler = makeDepositPaymentHandler(); + return zone.exoClass( 'LocalChainAccount', LocalChainAccountI, /** * @param {string} address * @param {AccountPowers} powers */ - (address, powers) => ({ address, ...powers, reserved: undefined }), + (address, powers) => ({ + address, + ...powers, + reserved: undefined, + }), { // Information that the account creator needs. getAddress() { return this.state.address; }, - /** @param {Brand<'nat'>} brand */ + /** + * @param {Brand<'nat'>} brand + * @returns {PromiseVow} + */ async getBalance(brand) { const { bank } = this.state; const purse = E(bank).getPurse(brand); @@ -67,20 +124,23 @@ const prepareLocalChainAccount = zone => * @param {Payment<'nat'>} payment * @param {Pattern} [optAmountShape] throws if the Amount of the Payment * does not match the provided Pattern - * @returns {Promise} + * @returns {PromiseVow>} */ async deposit(payment, optAmountShape) { const { bank } = this.state; - const allegedBrand = await E(payment).getAllegedBrand(); - const purse = E(bank).getPurse(allegedBrand); - return E(purse).deposit(payment, optAmountShape); + const getBrandWatcher = watch(E(payment).getAllegedBrand()); + return watch(getBrandWatcher, depositPaymentHandler, { + bank, + payment, + optAmountShape, + }); }, /** * Withdraw a payment from the account's bank purse of the amount's brand. * * @param {Amount<'nat'>} amount - * @returns {Promise>} payment + * @returns {PromiseVow>} payment */ async withdraw(amount) { const { bank } = this.state; @@ -94,7 +154,9 @@ const prepareLocalChainAccount = zone => * @template {TypedJson[]} MT messages tuple (use const with multiple * elements or it will be a mixed array) * @param {MT} messages - * @returns {Promise<{ [K in keyof MT]: JsonSafe> }>} + * @returns {PromiseVow<{ + * [K in keyof MT]: JsonSafe>; + * }>} */ async executeTx(messages) { const { address, system } = this.state; @@ -110,12 +172,17 @@ const prepareLocalChainAccount = zone => }; return E(system).toBridge(obj); }, + /** + * @param {TargetApp} tap + * @returns {PromiseVow} + */ async monitorTransfers(tap) { const { address, transfer } = this.state; return E(transfer).registerTap(address, tap); }, }, ); +}; /** * @typedef {Awaited< * ReturnType>> @@ -146,6 +213,8 @@ const prepareLocalChain = (zone, makeAccount) => * follows the ICA guidelines to help reduce collisions. See * x/vlocalchain/keeper/keeper.go AllocateAddress for the use of the app * hash and block data hash. + * + * @returns {PromiseVow} */ async makeAccount() { const { system, bankManager, transfer } = this.state; @@ -161,11 +230,11 @@ const prepareLocalChain = (zone, makeAccount) => * object. * * @param {import('@agoric/cosmic-proto').TypedJson} request - * @returns {Promise} + * @returns {PromiseVow} */ async query(request) { const requests = harden([request]); - const results = await E(this.self).queryMany(requests); + const results = await V(this.self).queryMany(requests); results.length === 1 || Fail`expected exactly one result; got ${results}`; const { error, reply } = results[0]; @@ -180,7 +249,7 @@ const prepareLocalChain = (zone, makeAccount) => * failure. * * @param {import('@agoric/cosmic-proto').TypedJson[]} requests - * @returns {Promise< + * @returns {PromiseVow< * { * error?: string; * reply: import('@agoric/cosmic-proto').TypedJson; @@ -197,9 +266,18 @@ const prepareLocalChain = (zone, makeAccount) => }, ); -/** @param {import('@agoric/base-zone').Zone} zone */ -export const prepareLocalChainTools = zone => { - const makeAccount = prepareLocalChainAccount(zone); +/** + * @param {import('@agoric/base-zone').Zone} zone + * @param {VowTools} vowTools + */ +export const prepareLocalChainTools = (zone, vowTools) => { + const makeDepositHandlers = prepareDepositPaymentHandler(zone); + const makeAccount = prepareLocalChainAccount( + zone, + makeDepositHandlers, + vowTools, + ); + const makeLocalChain = prepareLocalChain(zone, makeAccount); return harden({ makeLocalChain }); diff --git a/packages/vats/src/proposals/localchain-test.js b/packages/vats/src/proposals/localchain-test.js index 7cdb756a656..678690f6771 100644 --- a/packages/vats/src/proposals/localchain-test.js +++ b/packages/vats/src/proposals/localchain-test.js @@ -1,5 +1,5 @@ // @ts-check -import { E } from '@endo/far'; +import { V as E } from '@agoric/vow/vat.js'; import { typedJson } from '@agoric/cosmic-proto/vatsafe'; /** diff --git a/packages/vats/src/vat-localchain.js b/packages/vats/src/vat-localchain.js index 9f6471fb40c..501d94f0cd3 100644 --- a/packages/vats/src/vat-localchain.js +++ b/packages/vats/src/vat-localchain.js @@ -1,12 +1,17 @@ // @ts-check import { Far } from '@endo/far'; import { makeDurableZone } from '@agoric/zone/durable.js'; +import { prepareVowTools } from '@agoric/vow/vat.js'; import { prepareLocalChainTools } from './localchain.js'; export const buildRootObject = (_vatPowers, _args, baggage) => { const zone = makeDurableZone(baggage); - const { makeLocalChain } = prepareLocalChainTools(zone.subZone('localchain')); + const vowTools = prepareVowTools(zone.subZone('VowTools')); + const { makeLocalChain } = prepareLocalChainTools( + zone.subZone('localchain'), + vowTools, + ); return Far('LocalChainVat', { /** diff --git a/packages/vats/test/localchain.test.js b/packages/vats/test/localchain.test.js index 8606ea10a7b..55edb55e391 100644 --- a/packages/vats/test/localchain.test.js +++ b/packages/vats/test/localchain.test.js @@ -6,10 +6,9 @@ import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp'; import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; -import { E } from '@endo/far'; import { getInterfaceOf } from '@endo/marshal'; import { VTRANSFER_IBC_EVENT } from '@agoric/internal'; -import { prepareVowTools } from '@agoric/vow/vat.js'; +import { prepareVowTools, V as E } from '@agoric/vow/vat.js'; import { prepareLocalChainTools } from '../src/localchain.js'; import { prepareBridgeTargetModule } from '../src/bridge-target.js'; import { prepareTransferTools } from '../src/transfer.js'; @@ -71,7 +70,8 @@ const makeTestContext = async _t => { /** @param {LocalChainPowers} powers */ const makeLocalChain = async powers => { const zone = makeDurableZone(provideBaggage('localchain')); - return prepareLocalChainTools(zone).makeLocalChain(powers); + const vowTools = prepareVowTools(zone.subZone('vows')); + return prepareLocalChainTools(zone, vowTools).makeLocalChain(powers); }; const localchain = await makeLocalChain({