Skip to content

Commit 50b1d7e

Browse files
committed
fix: remove one layer of caching (the mailbox state)
1 parent bc765da commit 50b1d7e

File tree

5 files changed

+116
-86
lines changed

5 files changed

+116
-86
lines changed

packages/SwingSet/src/devices/mailbox.js

+40-14
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,36 @@ import Nat from '@agoric/nat';
7272
// replace it with one that tracks which parts of the state have been
7373
// modified, to build more efficient Merkle proofs.
7474

75-
export function buildMailboxStateMap() {
76-
const state = harden(new Map());
75+
export function importMailbox(data, inout = {}) {
76+
const outbox = new Map();
77+
data.outbox.forEach(m => {
78+
outbox.set(Nat(m[0]), m[1]);
79+
});
80+
inout.ack = data.ack;
81+
inout.outbox = outbox;
82+
return inout;
83+
}
84+
85+
export function exportMailbox(inout) {
86+
const messages = [];
87+
inout.outbox.forEach((body, msgnum) => {
88+
messages.push([msgnum, body]);
89+
});
90+
messages.sort((a, b) => a[0] - b[0]);
91+
return {
92+
ack: inout.ack,
93+
outbox: messages,
94+
};
95+
}
7796

97+
export function buildMailboxStateMap(state = harden(new Map())) {
7898
function getOrCreatePeer(peer) {
7999
if (!state.has(peer)) {
80-
state.set(peer, { outbox: harden(new Map()), inboundAck: 0 });
100+
const inout = {
101+
outbox: harden(new Map()),
102+
ack: 0,
103+
};
104+
state.set(peer, inout);
81105
}
82106
return state.get(peer);
83107
}
@@ -92,18 +116,17 @@ export function buildMailboxStateMap() {
92116
}
93117

94118
function setAcknum(peer, msgnum) {
95-
getOrCreatePeer(`${peer}`).inboundAck = Nat(msgnum);
119+
getOrCreatePeer(`${peer}`).ack = Nat(msgnum);
96120
}
97121

98122
function exportToData() {
99123
const data = {};
100124
state.forEach((inout, peer) => {
101-
const messages = [];
102-
inout.outbox.forEach((body, msgnum) => {
103-
messages.push([msgnum, body]);
104-
});
105-
messages.sort((a, b) => a[0] - b[0]);
106-
data[peer] = { outbox: messages, inboundAck: inout.inboundAck };
125+
const exported = exportMailbox(inout);
126+
data[peer] = {
127+
inboundAck: inout.ack,
128+
outbox: exported.outbox,
129+
};
107130
});
108131
return harden(data);
109132
}
@@ -115,10 +138,13 @@ export function buildMailboxStateMap() {
115138
for (const peer of Object.getOwnPropertyNames(data)) {
116139
const inout = getOrCreatePeer(peer);
117140
const d = data[peer];
118-
d.outbox.forEach(m => {
119-
inout.outbox.set(Nat(m[0]), m[1]);
120-
});
121-
inout.inboundAck = d.inboundAck;
141+
importMailbox(
142+
{
143+
ack: d.inboundAck,
144+
outbox: d.outbox,
145+
},
146+
inout,
147+
);
122148
}
123149
}
124150

packages/cosmic-swingset/lib/ag-solo/chain-cosmos-sdk.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ export async function connectToChain(
256256
log(`helper said: ${stdout}`);
257257
try {
258258
// Try to parse the stdout.
259-
return JSON.parse(JSON.parse(JSON.parse(stdout).value));
259+
return JSON.parse(JSON.parse(stdout).value);
260260
} catch (e) {
261261
log(`failed to parse output:`, e);
262262
}

packages/cosmic-swingset/lib/block-manager.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ export default function makeBlockManager({
151151

152152
// Always commit all the keeper state live.
153153
const start = Date.now();
154-
const { mailboxSize } = saveChainState();
155-
const mbTime = Date.now() - start;
154+
saveChainState();
155+
const chainTime = Date.now() - start;
156156

157157
// Advance our saved state variables.
158158
savedActions = currentActions;
@@ -167,7 +167,7 @@ export default function makeBlockManager({
167167
const saveTime = Date.now() - start2;
168168

169169
log.debug(
170-
`wrote SwingSet checkpoint (mailbox=${mailboxSize}), [run=${runTime}ms, mb=${mbTime}ms, save=${saveTime}ms]`,
170+
`wrote SwingSet checkpoint [run=${runTime}ms, chainSave=${chainTime}ms, outsideSave=${saveTime}ms]`,
171171
);
172172
currentActions = [];
173173

packages/cosmic-swingset/lib/chain-main.js

+66-43
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,69 @@
11
import stringify from '@agoric/swingset-vat/src/kernel/json-stable-stringify';
2+
import {
3+
importMailbox,
4+
exportMailbox,
5+
} from '@agoric/swingset-vat/src/devices/mailbox';
26

37
import { launch } from './launch-chain';
48
import makeBlockManager from './block-manager';
59

610
const AG_COSMOS_INIT = 'AG_COSMOS_INIT';
711

12+
const makeChainStorage = (call, prefix = '', imp = x => x, exp = x => x) => {
13+
let cache = new Map();
14+
let changedKeys = new Set();
15+
const storage = {
16+
has(key) {
17+
// It's more efficient just to get the value.
18+
const val = storage.get(key);
19+
return !!val;
20+
},
21+
set(key, obj) {
22+
if (cache.get(key) !== obj) {
23+
cache.set(key, obj);
24+
changedKeys.add(key);
25+
}
26+
},
27+
get(key) {
28+
if (cache.has(key)) {
29+
// Our cache has the value.
30+
return cache.get(key);
31+
}
32+
const retStr = call(stringify({ method: 'get', key: `${prefix}${key}` }));
33+
const ret = JSON.parse(retStr);
34+
const value = ret && JSON.parse(ret);
35+
// console.log(` value=${value}`);
36+
const obj = value && imp(value);
37+
cache.set(key, obj);
38+
// We need to add this in case the caller mutates the state, as in
39+
// mailbox.js, which mutates on basically every get.
40+
changedKeys.add(key);
41+
return obj;
42+
},
43+
commit() {
44+
for (const key of changedKeys.keys()) {
45+
const obj = cache.get(key);
46+
const value = stringify(exp(obj));
47+
call(
48+
stringify({
49+
method: 'set',
50+
key: `${prefix}${key}`,
51+
value,
52+
}),
53+
);
54+
}
55+
// Reset our state.
56+
storage.abort();
57+
},
58+
abort() {
59+
// Just reset our state.
60+
cache = new Map();
61+
changedKeys = new Set();
62+
},
63+
};
64+
return storage;
65+
};
66+
867
export default async function main(progname, args, { path, env, agcc }) {
968
const portNums = {};
1069

@@ -118,49 +177,13 @@ export default async function main(progname, args, { path, env, agcc }) {
118177
// so the 'externalStorage' object can close over the single mutable
119178
// instance, and we update the 'portNums.storage' value each time toSwingSet is called
120179
async function launchAndInitializeSwingSet() {
121-
// this object is used to store the mailbox state. we only ever use
122-
// key='mailbox'
123-
const mailboxStorage = {
124-
has(key) {
125-
// x/swingset/storage.go returns "true" or "false"
126-
const retStr = chainSend(
127-
portNums.storage,
128-
stringify({ method: 'has', key }),
129-
);
130-
const ret = JSON.parse(retStr);
131-
if (Boolean(ret) !== ret) {
132-
throw new Error(`chainSend(has) returned ${ret} not Boolean`);
133-
}
134-
return ret;
135-
},
136-
set(key, value) {
137-
if (value !== `${value}`) {
138-
throw new Error(
139-
`golang storage API only takes string values, not '${JSON.stringify(
140-
value,
141-
)}'`,
142-
);
143-
}
144-
const encodedValue = stringify(value);
145-
chainSend(
146-
portNums.storage,
147-
stringify({ method: 'set', key, value: encodedValue }),
148-
);
149-
},
150-
get(key) {
151-
const retStr = chainSend(
152-
portNums.storage,
153-
stringify({ method: 'get', key }),
154-
);
155-
// console.log(`s.get(${key}) retstr=${retstr}`);
156-
const encodedValue = JSON.parse(retStr);
157-
// console.log(` encodedValue=${encodedValue}`);
158-
const value = JSON.parse(encodedValue);
159-
// console.log(` value=${value}`);
160-
return value;
161-
},
162-
};
163-
180+
// this object is used to store the mailbox state.
181+
const mailboxStorage = makeChainStorage(
182+
msg => chainSend(portNums.storage, msg),
183+
'mailbox.',
184+
importMailbox,
185+
exportMailbox,
186+
);
164187
function doOutboundBridge(dstID, obj) {
165188
const portNum = portNums[dstID];
166189
if (portNum === undefined) {

packages/cosmic-swingset/lib/launch-chain.js

+6-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import path from 'path';
22
import anylogger from 'anylogger';
33

4-
import djson from 'deterministic-json';
54
import {
65
buildMailbox,
76
buildMailboxStateMap,
@@ -18,7 +17,7 @@ const log = anylogger('launch-chain');
1817
const SWING_STORE_META_KEY = 'cosmos/meta';
1918

2019
async function buildSwingset(
21-
mailboxState,
20+
mailboxStorage,
2221
bridgeOutbound,
2322
storage,
2423
vatsDir,
@@ -30,8 +29,7 @@ async function buildSwingset(
3029
if (config === null) {
3130
config = loadBasedir(vatsDir);
3231
}
33-
const mbs = buildMailboxStateMap();
34-
mbs.populateFromData(mailboxState);
32+
const mbs = buildMailboxStateMap(mailboxStorage);
3533
const timer = buildTimer();
3634
const mb = buildMailbox(mbs);
3735
const bd = buildBridge(bridgeOutbound);
@@ -48,7 +46,7 @@ async function buildSwingset(
4846
await controller.run();
4947

5048
const bridgeInbound = bd.deliverInbound;
51-
return { controller, mb, mbs, bridgeInbound, timer };
49+
return { controller, mb, bridgeInbound, timer };
5250
}
5351

5452
export async function launch(
@@ -62,11 +60,6 @@ export async function launch(
6260
) {
6361
log.info('Launching SwingSet kernel');
6462

65-
log(`checking for saved mailbox state`, mailboxStorage.has('mailbox'));
66-
const mailboxState = mailboxStorage.has('mailbox')
67-
? JSON.parse(mailboxStorage.get('mailbox'))
68-
: {};
69-
7063
const tempdir = path.resolve(kernelStateDBDir, 'check-lmdb-tempdir');
7164
const { openSwingStore } = getBestSwingStore(tempdir);
7265
const { storage, commit } = openSwingStore(kernelStateDBDir);
@@ -76,8 +69,8 @@ export async function launch(
7669
return doOutboundBridge(dstID, obj);
7770
}
7871
log.debug(`buildSwingset`);
79-
const { controller, mb, mbs, bridgeInbound, timer } = await buildSwingset(
80-
mailboxState,
72+
const { controller, mb, bridgeInbound, timer } = await buildSwingset(
73+
mailboxStorage,
8174
bridgeOutbound,
8275
storage,
8376
vatsDir,
@@ -86,20 +79,8 @@ export async function launch(
8679
);
8780

8881
function saveChainState() {
89-
// now check mbs
90-
const newState = mbs.exportToData();
91-
const newData = djson.stringify(newState);
92-
9382
// Save the mailbox state.
94-
for (const peer of Object.getOwnPropertyNames(newState)) {
95-
const data = {
96-
outbox: newState[peer].outbox,
97-
ack: newState[peer].inboundAck,
98-
};
99-
mailboxStorage.set(`mailbox.${peer}`, djson.stringify(data));
100-
}
101-
mailboxStorage.set('mailbox', newData);
102-
return { mailboxSize: newData.length };
83+
mailboxStorage.commit();
10384
}
10485

10586
function saveOutsideState(savedHeight, savedActions) {

0 commit comments

Comments
 (0)