Skip to content

Commit

Permalink
fix: order preserving encoding trick, aha
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Nov 19, 2022
1 parent 9ed9b07 commit 7f1c1bc
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 16 deletions.
63 changes: 47 additions & 16 deletions packages/marshal/test/test-encodePassable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,74 @@ import { compareRank, makeComparatorKit } from '../src/rankOrder.js';
import { sample } from './test-rankOrder.js';
import { arbPassable } from '../tools/arb-passable.js';

const { details: X } = assert;
const { Fail, quote: q } = assert;

const r2e = new Map();
const e2r = [];
const buffers = {
__proto__: null,
r: [],
'?': [],
'!': [],
};
const resetBuffers = () => {
buffers.r = [];
buffers['?'] = [];
buffers['!'] = [];
};
const cursors = {
__proto__: null,
r: 0,
'?': 0,
'!': 0,
};
const resetCursors = () => {
cursors.r = 0;
cursors['?'] = 0;
cursors['!'] = 0;
};

const encodeThing = (prefix, r) => {
if (r2e.has(r)) {
return r2e.get(r);
}
const result = `${prefix}${e2r.length}`;
r2e.set(r, result);
e2r.push(r);
return result;
buffers[prefix].push(r);
// With this encoding, all things with the same prefix have the same rank
return prefix;
};

const decodeThing = (prefix, e) => {
assert(e.startsWith(prefix), X`unexpected encoding ${e}`);
const i = Number(BigInt(e.substring(1)));
assert(i >= 0 && i < e2r.length);
return e2r[i];
prefix === e ||
Fail`expected encoding ${q(e)} to simply be the prefix ${q(prefix)}`;
(cursors[prefix] >= 0 && cursors[prefix] < buffers[prefix].length) ||
Fail`while decoding ${q(e)}, expected cursors[${q(prefix)}], i.e., ${q(
cursors[prefix],
)} <= ${q(buffers[prefix].length)}`;
const thing = buffers[prefix][cursors[prefix]];
cursors[prefix] += 1;
return thing;
};

const compareRemotables = (x, y) =>
compareRank(encodeThing('r', x), encodeThing('r', y));

const encodePassable = makeEncodePassable({
const encodePassableInternal = makeEncodePassable({
encodeRemotable: r => encodeThing('r', r),
encodePromise: p => encodeThing('?', p),
encodeError: er => encodeThing('!', er),
});

const decodePassable = makeDecodePassable({
const encodePassable = passable => {
resetBuffers();
return encodePassableInternal(passable);
};

const decodePassableInternal = makeDecodePassable({
decodeRemotable: e => decodeThing('r', e),
decodePromise: e => decodeThing('?', e),
decodeError: e => decodeThing('!', e),
});

const decodePassable = encoded => {
resetCursors();
return decodePassableInternal(encoded);
};

const { comparator: compareFull } = makeComparatorKit(compareRemotables);

const asNumber = new Float64Array(1);
Expand Down
8 changes: 8 additions & 0 deletions packages/marshal/test/test-rankOrder.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ export const sample = harden([
[5, { foo: 4, bar: undefined }],
Promise.resolve('fulfillment'),
[5, { foo: 4 }],
// The promises should be of the same rank, in which case
// the singleton array should be earlier. But if the encoded
// gives the earlier promise an earlier encoding (as it used to),
// then the encoded forms will not be order preserving.
[Promise.resolve(null), 'x'],
[Promise.resolve(null)],
]);

const rejectedP = Promise.reject(new Error('broken'));
Expand Down Expand Up @@ -207,6 +213,8 @@ const sortedSample = harden([
// Lexicographic records by reverse sorted property name, then by values
// in that order.
[],
[Promise.resolve(null)],
[Promise.resolve(null), 'x'],
[5],
[5, { bar: 5 }],
[5, { foo: 4 }],
Expand Down

0 comments on commit 7f1c1bc

Please sign in to comment.