Files
actual/packages/crdt/src/crdt/merkle.test.ts
2024-08-20 17:18:54 +01:00

201 lines
7.3 KiB
TypeScript

import * as merkle from './merkle';
import { Timestamp } from './timestamp';
function message(timestampStr: string, hash: number) {
const timestamp = Timestamp.parse(timestampStr)!;
timestamp.hash = () => hash;
return { timestamp };
}
function insertMessages(
trie: merkle.TrieNode,
messages: ReturnType<typeof message>[],
) {
messages.forEach(msg => {
trie = merkle.insert(trie, msg.timestamp);
});
return trie;
}
describe('merkle trie', () => {
test('adding an item works', () => {
let trie = merkle.insert(
merkle.emptyTrie(),
Timestamp.parse('2018-11-12T13:21:40.122Z-0000-0123456789ABCDEF')!,
);
trie = merkle.insert(
trie,
Timestamp.parse('2018-11-13T13:21:40.122Z-0000-0123456789ABCDEF')!,
);
expect(trie).toMatchSnapshot();
});
test('diff returns the correct time difference', () => {
let trie1 = merkle.emptyTrie();
let trie2 = merkle.emptyTrie();
const messages = [
// First client messages
message('2018-11-13T13:20:40.122Z-0000-0123456789ABCDEF', 1000),
message('2018-11-14T13:05:35.122Z-0000-0123456789ABCDEF', 1100),
message('2018-11-15T22:19:00.122Z-0000-0123456789ABCDEF', 1200),
// Second client messages
message('2018-11-20T13:19:40.122Z-0000-0123456789ABCDEF', 1300),
message('2018-11-25T13:19:40.122Z-0000-0123456789ABCDEF', 1400),
];
trie1 = merkle.insert(trie1, messages[0].timestamp);
trie1 = merkle.insert(trie1, messages[1].timestamp);
trie1 = merkle.insert(trie1, messages[2].timestamp);
expect(trie1.hash).toBe(788);
trie2 = merkle.insert(trie2, messages[3].timestamp);
trie2 = merkle.insert(trie2, messages[4].timestamp);
expect(trie2.hash).toBe(108);
expect(new Date(merkle.diff(trie1, trie2)!).toISOString()).toBe(
'2018-11-02T17:15:00.000Z',
);
trie1 = merkle.insert(trie1, messages[3].timestamp);
trie1 = merkle.insert(trie1, messages[4].timestamp);
trie2 = merkle.insert(trie2, messages[0].timestamp);
trie2 = merkle.insert(trie2, messages[1].timestamp);
trie2 = merkle.insert(trie2, messages[2].timestamp);
expect(trie1.hash).toBe(888);
expect(trie1.hash).toBe(trie2.hash);
});
test('diffing works with empty tries', () => {
const trie1 = merkle.emptyTrie();
const trie2 = merkle.insert(
merkle.emptyTrie(),
Timestamp.parse('2009-01-02T10:17:37.789Z-0000-0000testinguuid1')!,
);
expect(merkle.diff(trie1, trie2)).toBe(0);
});
test('pruning works and keeps correct hashes', () => {
const messages = [
message('2018-11-01T01:00:00.000Z-0000-0123456789ABCDEF', 1000),
message('2018-11-01T01:09:00.000Z-0000-0123456789ABCDEF', 1100),
message('2018-11-01T01:18:00.000Z-0000-0123456789ABCDEF', 1200),
message('2018-11-01T01:27:00.000Z-0000-0123456789ABCDEF', 1300),
message('2018-11-01T01:36:00.000Z-0000-0123456789ABCDEF', 1400),
message('2018-11-01T01:45:00.000Z-0000-0123456789ABCDEF', 1500),
message('2018-11-01T01:54:00.000Z-0000-0123456789ABCDEF', 1600),
message('2018-11-01T02:03:00.000Z-0000-0123456789ABCDEF', 1700),
message('2018-11-01T02:10:00.000Z-0000-0123456789ABCDEF', 1800),
message('2018-11-01T02:19:00.000Z-0000-0123456789ABCDEF', 1900),
message('2018-11-01T02:28:00.000Z-0000-0123456789ABCDEF', 2000),
message('2018-11-01T02:37:00.000Z-0000-0123456789ABCDEF', 2100),
];
let trie = merkle.emptyTrie();
messages.forEach(msg => {
trie = merkle.insert(trie, msg.timestamp);
});
expect(trie.hash).toBe(2496);
expect(trie).toMatchSnapshot();
const pruned = merkle.prune(trie);
expect(pruned.hash).toBe(2496);
expect(pruned).toMatchSnapshot();
});
test('diffing differently shaped tries returns correct time', () => {
const messages = [
message('2018-11-01T01:00:00.000Z-0000-0123456789ABCDEF', 1000),
message('2018-11-01T01:09:00.000Z-0000-0123456789ABCDEF', 1100),
message('2018-11-01T01:18:00.000Z-0000-0123456789ABCDEF', 1200),
message('2018-11-01T01:27:00.000Z-0000-0123456789ABCDEF', 1300),
message('2018-11-01T01:36:00.000Z-0000-0123456789ABCDEF', 1400),
message('2018-11-01T01:45:00.000Z-0000-0123456789ABCDEF', 1500),
message('2018-11-01T01:54:00.000Z-0000-0123456789ABCDEF', 1600),
message('2018-11-01T02:03:00.000Z-0000-0123456789ABCDEF', 1700),
message('2018-11-01T02:10:00.000Z-0000-0123456789ABCDEF', 1800),
message('2018-11-01T02:19:00.000Z-0000-0123456789ABCDEF', 1900),
message('2018-11-01T02:28:00.000Z-0000-0123456789ABCDEF', 2000),
message('2018-11-01T02:37:00.000Z-0000-0123456789ABCDEF', 2100),
];
const trie = insertMessages({}, messages);
// Case 0: It always returns a base time when comparing with an
// empty trie
expect(new Date(merkle.diff(merkle.emptyTrie(), trie)!).toISOString()).toBe(
'1970-01-01T00:00:00.000Z',
);
expect(new Date(merkle.diff(trie, merkle.emptyTrie())!).toISOString()).toBe(
'1970-01-01T00:00:00.000Z',
);
// Case 1: Add an older message that modifies the trie in such a
// way that it modifies the 1st out of 3 branches (so it will be
// pruned away)
const trie1 = insertMessages(trie, [
message('2018-11-01T00:59:00.000Z-0000-0123456789ABCDEF', 900),
]);
// Normal comparison works
expect(new Date(merkle.diff(trie1, trie)!).toISOString()).toBe(
'2018-11-01T00:54:00.000Z',
);
// Comparing the pruned new trie is lossy, so it returns an even older time
expect(
new Date(merkle.diff(merkle.prune(trie1), trie)!).toISOString(),
).toBe('2018-11-01T00:45:00.000Z');
// Comparing the pruned original trie is just as lossy
expect(
new Date(merkle.diff(trie1, merkle.prune(trie))!).toISOString(),
).toBe('2018-11-01T00:45:00.000Z');
// Pruning both tries is just as lossy as well, since the changed
// key is pruned away in both cases and it won't find a changed
// key so it bails at the point
expect(
new Date(
merkle.diff(merkle.prune(trie1), merkle.prune(trie))!,
).toISOString(),
).toBe('2018-11-01T00:45:00.000Z');
// Case 2: Add two messages similar to the above case, but the
// second message modifies the 2nd key at the same level as the
// first message modifying the 1st key
const trie2 = insertMessages(trie, [
message('2018-11-01T00:59:00.000Z-0000-0123456789ABCDEF', 900),
message('2018-11-01T01:15:00.000Z-0000-0123456789ABCDEF', 1422),
]);
// Normal comparison works
expect(new Date(merkle.diff(trie2, trie)!).toISOString()).toBe(
'2018-11-01T00:54:00.000Z',
);
// Same as case 1
expect(
new Date(merkle.diff(merkle.prune(trie2), trie)!).toISOString(),
).toBe('2018-11-01T00:45:00.000Z');
// Same as case 1
expect(
new Date(merkle.diff(trie2, merkle.prune(trie))!).toISOString(),
).toBe('2018-11-01T00:45:00.000Z');
// Pruning both tries is very lossy and this ends up returning a
// time that only covers the second message. Syncing will need
// multiple passes to sync up. This happens because the second
// message provides a "changed path" that the diff takes which
// ignores the first message.
expect(
new Date(
merkle.diff(merkle.prune(trie2), merkle.prune(trie))!,
).toISOString(),
).toBe('2018-11-01T01:12:00.000Z');
});
});