diff --git a/packages/SwingSet/src/devices/mailbox.js b/packages/SwingSet/src/devices/mailbox.js index 72ae9f414d3..2feb9660951 100644 --- a/packages/SwingSet/src/devices/mailbox.js +++ b/packages/SwingSet/src/devices/mailbox.js @@ -72,12 +72,36 @@ import Nat from '@agoric/nat'; // replace it with one that tracks which parts of the state have been // modified, to build more efficient Merkle proofs. -export function buildMailboxStateMap() { - const state = harden(new Map()); +export function importMailbox(data, inout = {}) { + const outbox = new Map(); + data.outbox.forEach(m => { + outbox.set(Nat(m[0]), m[1]); + }); + inout.ack = data.ack; + inout.outbox = outbox; + return inout; +} + +export function exportMailbox(inout) { + const messages = []; + inout.outbox.forEach((body, msgnum) => { + messages.push([msgnum, body]); + }); + messages.sort((a, b) => a[0] - b[0]); + return { + ack: inout.ack, + outbox: messages, + }; +} +export function buildMailboxStateMap(state = harden(new Map())) { function getOrCreatePeer(peer) { if (!state.has(peer)) { - state.set(peer, { outbox: harden(new Map()), inboundAck: 0 }); + const inout = { + outbox: harden(new Map()), + ack: 0, + }; + state.set(peer, inout); } return state.get(peer); } @@ -92,18 +116,17 @@ export function buildMailboxStateMap() { } function setAcknum(peer, msgnum) { - getOrCreatePeer(`${peer}`).inboundAck = Nat(msgnum); + getOrCreatePeer(`${peer}`).ack = Nat(msgnum); } function exportToData() { const data = {}; state.forEach((inout, peer) => { - const messages = []; - inout.outbox.forEach((body, msgnum) => { - messages.push([msgnum, body]); - }); - messages.sort((a, b) => a[0] - b[0]); - data[peer] = { outbox: messages, inboundAck: inout.inboundAck }; + const exported = exportMailbox(inout); + data[peer] = { + inboundAck: inout.ack, + outbox: exported.outbox, + }; }); return harden(data); } @@ -115,10 +138,13 @@ export function buildMailboxStateMap() { for (const peer of Object.getOwnPropertyNames(data)) { const inout = getOrCreatePeer(peer); const d = data[peer]; - d.outbox.forEach(m => { - inout.outbox.set(Nat(m[0]), m[1]); - }); - inout.inboundAck = d.inboundAck; + importMailbox( + { + ack: d.inboundAck, + outbox: d.outbox, + }, + inout, + ); } } diff --git a/packages/cosmic-swingset/lib/ag-solo/chain-cosmos-sdk.js b/packages/cosmic-swingset/lib/ag-solo/chain-cosmos-sdk.js index 79e93f51063..1a4ba3e10fa 100644 --- a/packages/cosmic-swingset/lib/ag-solo/chain-cosmos-sdk.js +++ b/packages/cosmic-swingset/lib/ag-solo/chain-cosmos-sdk.js @@ -256,7 +256,7 @@ export async function connectToChain( log(`helper said: ${stdout}`); try { // Try to parse the stdout. - return JSON.parse(JSON.parse(JSON.parse(stdout).value)); + return JSON.parse(JSON.parse(stdout).value); } catch (e) { log(`failed to parse output:`, e); } diff --git a/packages/cosmic-swingset/lib/block-manager.js b/packages/cosmic-swingset/lib/block-manager.js index 31810622475..9f6821a9865 100644 --- a/packages/cosmic-swingset/lib/block-manager.js +++ b/packages/cosmic-swingset/lib/block-manager.js @@ -151,8 +151,8 @@ export default function makeBlockManager({ // Always commit all the keeper state live. const start = Date.now(); - const { mailboxSize } = saveChainState(); - const mbTime = Date.now() - start; + saveChainState(); + const chainTime = Date.now() - start; // Advance our saved state variables. savedActions = currentActions; @@ -167,7 +167,7 @@ export default function makeBlockManager({ const saveTime = Date.now() - start2; log.debug( - `wrote SwingSet checkpoint (mailbox=${mailboxSize}), [run=${runTime}ms, mb=${mbTime}ms, save=${saveTime}ms]`, + `wrote SwingSet checkpoint [run=${runTime}ms, chainSave=${chainTime}ms, outsideSave=${saveTime}ms]`, ); currentActions = []; diff --git a/packages/cosmic-swingset/lib/chain-main.js b/packages/cosmic-swingset/lib/chain-main.js index 9738aca5ed5..a2a7bc8d941 100644 --- a/packages/cosmic-swingset/lib/chain-main.js +++ b/packages/cosmic-swingset/lib/chain-main.js @@ -1,10 +1,69 @@ import stringify from '@agoric/swingset-vat/src/kernel/json-stable-stringify'; +import { + importMailbox, + exportMailbox, +} from '@agoric/swingset-vat/src/devices/mailbox'; import { launch } from './launch-chain'; import makeBlockManager from './block-manager'; const AG_COSMOS_INIT = 'AG_COSMOS_INIT'; +const makeChainStorage = (call, prefix = '', imp = x => x, exp = x => x) => { + let cache = new Map(); + let changedKeys = new Set(); + const storage = { + has(key) { + // It's more efficient just to get the value. + const val = storage.get(key); + return !!val; + }, + set(key, obj) { + if (cache.get(key) !== obj) { + cache.set(key, obj); + changedKeys.add(key); + } + }, + get(key) { + if (cache.has(key)) { + // Our cache has the value. + return cache.get(key); + } + const retStr = call(stringify({ method: 'get', key: `${prefix}${key}` })); + const ret = JSON.parse(retStr); + const value = ret && JSON.parse(ret); + // console.log(` value=${value}`); + const obj = value && imp(value); + cache.set(key, obj); + // We need to add this in case the caller mutates the state, as in + // mailbox.js, which mutates on basically every get. + changedKeys.add(key); + return obj; + }, + commit() { + for (const key of changedKeys.keys()) { + const obj = cache.get(key); + const value = stringify(exp(obj)); + call( + stringify({ + method: 'set', + key: `${prefix}${key}`, + value, + }), + ); + } + // Reset our state. + storage.abort(); + }, + abort() { + // Just reset our state. + cache = new Map(); + changedKeys = new Set(); + }, + }; + return storage; +}; + export default async function main(progname, args, { path, env, agcc }) { const portNums = {}; @@ -118,49 +177,13 @@ export default async function main(progname, args, { path, env, agcc }) { // so the 'externalStorage' object can close over the single mutable // instance, and we update the 'portNums.storage' value each time toSwingSet is called async function launchAndInitializeSwingSet() { - // this object is used to store the mailbox state. we only ever use - // key='mailbox' - const mailboxStorage = { - has(key) { - // x/swingset/storage.go returns "true" or "false" - const retStr = chainSend( - portNums.storage, - stringify({ method: 'has', key }), - ); - const ret = JSON.parse(retStr); - if (Boolean(ret) !== ret) { - throw new Error(`chainSend(has) returned ${ret} not Boolean`); - } - return ret; - }, - set(key, value) { - if (value !== `${value}`) { - throw new Error( - `golang storage API only takes string values, not '${JSON.stringify( - value, - )}'`, - ); - } - const encodedValue = stringify(value); - chainSend( - portNums.storage, - stringify({ method: 'set', key, value: encodedValue }), - ); - }, - get(key) { - const retStr = chainSend( - portNums.storage, - stringify({ method: 'get', key }), - ); - // console.log(`s.get(${key}) retstr=${retstr}`); - const encodedValue = JSON.parse(retStr); - // console.log(` encodedValue=${encodedValue}`); - const value = JSON.parse(encodedValue); - // console.log(` value=${value}`); - return value; - }, - }; - + // this object is used to store the mailbox state. + const mailboxStorage = makeChainStorage( + msg => chainSend(portNums.storage, msg), + 'mailbox.', + importMailbox, + exportMailbox, + ); function doOutboundBridge(dstID, obj) { const portNum = portNums[dstID]; if (portNum === undefined) { diff --git a/packages/cosmic-swingset/lib/launch-chain.js b/packages/cosmic-swingset/lib/launch-chain.js index a671b0e5642..7bb071d5b86 100644 --- a/packages/cosmic-swingset/lib/launch-chain.js +++ b/packages/cosmic-swingset/lib/launch-chain.js @@ -1,7 +1,6 @@ import path from 'path'; import anylogger from 'anylogger'; -import djson from 'deterministic-json'; import { buildMailbox, buildMailboxStateMap, @@ -18,7 +17,7 @@ const log = anylogger('launch-chain'); const SWING_STORE_META_KEY = 'cosmos/meta'; async function buildSwingset( - mailboxState, + mailboxStorage, bridgeOutbound, storage, vatsDir, @@ -30,8 +29,7 @@ async function buildSwingset( if (config === null) { config = loadBasedir(vatsDir); } - const mbs = buildMailboxStateMap(); - mbs.populateFromData(mailboxState); + const mbs = buildMailboxStateMap(mailboxStorage); const timer = buildTimer(); const mb = buildMailbox(mbs); const bd = buildBridge(bridgeOutbound); @@ -48,7 +46,7 @@ async function buildSwingset( await controller.run(); const bridgeInbound = bd.deliverInbound; - return { controller, mb, mbs, bridgeInbound, timer }; + return { controller, mb, bridgeInbound, timer }; } export async function launch( @@ -62,11 +60,6 @@ export async function launch( ) { log.info('Launching SwingSet kernel'); - log(`checking for saved mailbox state`, mailboxStorage.has('mailbox')); - const mailboxState = mailboxStorage.has('mailbox') - ? JSON.parse(mailboxStorage.get('mailbox')) - : {}; - const tempdir = path.resolve(kernelStateDBDir, 'check-lmdb-tempdir'); const { openSwingStore } = getBestSwingStore(tempdir); const { storage, commit } = openSwingStore(kernelStateDBDir); @@ -76,8 +69,8 @@ export async function launch( return doOutboundBridge(dstID, obj); } log.debug(`buildSwingset`); - const { controller, mb, mbs, bridgeInbound, timer } = await buildSwingset( - mailboxState, + const { controller, mb, bridgeInbound, timer } = await buildSwingset( + mailboxStorage, bridgeOutbound, storage, vatsDir, @@ -86,20 +79,8 @@ export async function launch( ); function saveChainState() { - // now check mbs - const newState = mbs.exportToData(); - const newData = djson.stringify(newState); - // Save the mailbox state. - for (const peer of Object.getOwnPropertyNames(newState)) { - const data = { - outbox: newState[peer].outbox, - ack: newState[peer].inboundAck, - }; - mailboxStorage.set(`mailbox.${peer}`, djson.stringify(data)); - } - mailboxStorage.set('mailbox', newData); - return { mailboxSize: newData.length }; + mailboxStorage.commit(); } function saveOutsideState(savedHeight, savedActions) {