Skip to content

Commit

Permalink
fix: traversal optimization when checking if x contains y (#34)
Browse files Browse the repository at this point in the history
* continue if seen

* test for contains seen

* new clock test to fail without optimization

* formatter

* test name

* more realistic forking?

* refactor: simplify test

---------

Co-authored-by: Chris Anderson <jchris@gmail.com>
Co-authored-by: valorant-dhruv <78591597+valorant-dhruv@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 10, 2024
1 parent bfe30a5 commit 3ef028a
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/clock/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,16 @@ 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
if (link.toString() === b.toString()) return true
// 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)
}
Expand Down
83 changes: 83 additions & 0 deletions test/clock.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<any>[]} */
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')
})
})

0 comments on commit 3ef028a

Please sign in to comment.