diff --git a/src/clock/index.js b/src/clock/index.js index 9eae784a..30a41774 100644 --- a/src/clock/index.js +++ b/src/clock/index.js @@ -122,6 +122,7 @@ const contains = async (events, a, b) => { if (a.toString() === b.toString()) return true const [{ value: aevent }, { value: bevent }] = await Promise.all([events.get(a), events.get(b)]) const links = [...aevent.parents] + const seen = new Set() while (links.length) { const link = links.shift() if (!link) break @@ -129,6 +130,8 @@ const contains = async (events, a, b) => { // if any of b's parents are this link, then b cannot exist in any of the // tree below, since that would create a cycle. if (bevent.parents.some(p => link.toString() === p.toString())) continue + if (seen.has(link.toString())) continue + seen.add(link.toString()) const { value: event } = await events.get(link) links.push(...event.parents) } diff --git a/test/clock.test.js b/test/clock.test.js index 52b22d1a..b819e04d 100644 --- a/test/clock.test.js +++ b/test/clock.test.js @@ -146,6 +146,12 @@ describe('clock', () => { it('add an old event', async () => { const blocks = new Blockstore() + const blockGet = blocks.get.bind(blocks) + let count = 0 + blocks.get = async cid => { + count++ + return blockGet(cid) + } const root = await EventBlock.create(await randomEventData()) await blocks.put(root.cid, root.bytes) @@ -184,7 +190,9 @@ describe('clock', () => { // now very old one const event6 = await EventBlock.create(await randomEventData(), parents0) await blocks.put(event6.cid, event6.bytes) + const before = count head = await advance(blocks, head, event6.cid) + assert.equal(count - before, 10) for await (const line of vis(blocks, head)) console.log(line) assert.equal(head.length, 2) @@ -212,4 +220,79 @@ describe('clock', () => { assert.equal(head.length, 1) assert.equal(head[0].toString(), event1.cid.toString()) }) + + + /* + ```mermaid + flowchart TB + event3 --> event1 + event3 --> event2 + event2 --> event0 + event1 --> event0 + event0 --> genesis + event4 --> genesis + ``` + + All we want to do is prove that `event0` and `genesis` are not fetched + multiple times, since there are multiple paths to it in the tree. + + We arrive at 8, because when we advance with `head: [event3], event: event4` + we firstly check if the head is in the event: + + * get event4 (1) + * get event3 (2) + * get genesis (3) + + Then we check if the event is in the head, with de-duping: + + * get event3 (4) + * get event4 (5) + * then get each of the nodes back to parent(s) of `event4` (`genesis`): + * event1 (6) + * event2 (7) + * event0 (8) + * (we don't fetch genesis due to existing cycle detection) + + Without deduping, we expect 9 node fetches, since we traverse across `event0` + again, since it is linked to by 2 nodes. + */ + it('contains only traverses history once', async () => { + const blocks = new Blockstore() + const genesis = await EventBlock.create(await randomEventData()) + await blocks.put(genesis.cid, genesis.bytes) + /** @type {API.EventLink[]} */ + let head = [genesis.cid] + const blockGet = blocks.get.bind(blocks) + let count = 0 + blocks.get = async cid => { + count++ + return blockGet(cid) + } + + const event0 = await EventBlock.create(await randomEventData(), [genesis.cid]) + await blocks.put(event0.cid, event0.bytes) + head = await advance(blocks, head, event0.cid) + + const event1 = await EventBlock.create(await randomEventData(), [event0.cid]) + await blocks.put(event1.cid, event1.bytes) + head = await advance(blocks, head, event1.cid) + + const event2 = await EventBlock.create(await randomEventData(), [event0.cid]) + await blocks.put(event2.cid, event2.bytes) + head = await advance(blocks, head, event2.cid) + + const event3 = await EventBlock.create(await randomEventData(), [event1.cid, event2.cid]) + await blocks.put(event3.cid, event3.bytes) + head = await advance(blocks, head, event3.cid) + + const before = count + const event4 = await EventBlock.create(await randomEventData(), [genesis.cid]) + await blocks.put(event4.cid, event4.bytes) + head = await advance(blocks, head, event4.cid) + + assert.equal(head.length, 2) + assert.equal(head[1].toString(), event4.cid.toString()) + assert.equal(head[0].toString(), event3.cid.toString()) + assert.equal(count - before, 8, 'The number of traversals should be 8 with optimization') + }) })