Skip to content

Commit

Permalink
Merge pull request #141 from ssbc/4.7_split
Browse files Browse the repository at this point in the history
4.7 split membership
  • Loading branch information
mixmix authored Jan 25, 2024
2 parents 2a4f9c7 + df5954e commit 2713f04
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 13 deletions.
11 changes: 4 additions & 7 deletions lib/epochs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,19 +465,16 @@ function BuildPreferredEpoch(ssb, groupId) {
}

// case 4.6 - groups have overlapping membership, but disjoint
else if (
intersection(members0, members1).size > 0
) {
else if (intersection(members0, members1).size > 0) {
// choose one for now,
preferredEpoch = tieBreak(tips)
// but also kick off resolution
fixDisjointEpochsLater(ssb, groupId) // <<< DELAYED SIDE EFFECTS!
} else {
return cb(new Error('unknown membership case!'))
}

// case 4.7 - disjoint membership (no overlap!)

// prettier-ignore
else return cb(Error('Membership case not handled yet'))
// in this case peers should not even know about 2 distinct memberships
} else return cb(Error(`case of ${tips.length} tips not handled yet`))

if (preferredEpoch.members) delete preferredEpoch.members
Expand Down
105 changes: 99 additions & 6 deletions test/lib/epochs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ function getRootIds(peers) {
peers.map((peer) => p(peer.metafeeds.findOrCreate)())
).then((feeds) => feeds.map((feed) => feed.id))
}
function closeAll(peers) {
return Promise.all(peers.map((peer) => p(peer.close)(true)))
}

test('lib/epochs (getEpochs, getMembers)', async (t) => {
const run = Run(t)
Expand All @@ -26,7 +29,7 @@ test('lib/epochs (getEpochs, getMembers)', async (t) => {
async function sync(label) {
return run(`(sync ${label})`, replicate(peers), { isTest: false })
}
t.teardown(() => peers.forEach((peer) => peer.close(true)))
t.teardown(async () => await closeAll(peers))

const [aliceId, bobId, oscarId] = await getRootIds(peers)
await run(
Expand Down Expand Up @@ -153,7 +156,7 @@ test('lib/epochs (getMissingMembers)', async (t) => {
{ isTest: false }
)
}
t.teardown(() => peers.forEach((peer) => peer.close(true)))
t.teardown(async () => await closeAll(peers))

await run(
'start tribes',
Expand Down Expand Up @@ -260,7 +263,7 @@ test('lib/epochs (getPreferredEpoch - 4.4. same membership)', async (t) => {
Server({ name: 'bob' }),
Server({ name: 'oscar' }),
]
t.teardown(() => peers.forEach((peer) => peer.close(true)))
t.teardown(async () => await closeAll(peers))

const [alice, bob, oscar] = peers
const [bobId, oscarId] = await getRootIds([bob, oscar])
Expand Down Expand Up @@ -381,7 +384,7 @@ test('lib/epochs (getPreferredEpoch - 4.5. subset membership)', async (t) => {
Server({ name: 'carol' }),
Server({ name: 'oscar' }),
]
t.teardown(() => peers.forEach((peer) => peer.close(true)))
t.teardown(async () => await closeAll(peers))

const [alice, bob, carol, oscar] = peers
const [bobId, carolId, oscarId] = await getRootIds([bob, carol, oscar])
Expand Down Expand Up @@ -457,7 +460,7 @@ test('lib/epochs (getPreferredEpoch - 4.6. overlapping membership)', async (t) =
Server({ name: 'carol' }),
Server({ name: 'oscar' }),
]
t.teardown(() => peers.forEach((peer) => peer.close(true)))
t.teardown(async () => await closeAll(peers))

const [alice, bob, carol, oscar] = peers
const [bobId, carolId, oscarId] = await getRootIds([bob, carol, oscar])
Expand All @@ -481,6 +484,7 @@ test('lib/epochs (getPreferredEpoch - 4.6. overlapping membership)', async (t) =
'others accept invites',
Promise.all([
bob.tribes2.acceptInvite(group.id),
carol.tribes2.acceptInvite(group.id),
oscar.tribes2.acceptInvite(group.id),
])
)
Expand Down Expand Up @@ -520,8 +524,97 @@ test('lib/epochs (getPreferredEpoch - 4.6. overlapping membership)', async (t) =
t.end()
})

test.skip('lib/epochs (getPreferredEpoch - 4.7. disjoint membership)', async (t) => {
test('lib/epochs (getPreferredEpoch - 4.7. disjoint membership)', async (t) => {
// there is no conflict in this case (doesn't need testing?)

// alice starts a group, adds bob, carol, oscar
// simultaneously:
// - alice excludes carol, oscar
// - oscar excludes alice, bob
//
// the group split!

const run = Run(t)

// <setup>
const peers = [
Server({ name: 'alice' }),
Server({ name: 'bob' }),
Server({ name: 'carol' }),
Server({ name: 'oscar' }),
]
t.teardown(async () => await closeAll(peers))

const [alice, bob, carol, oscar] = peers
const [aliceId, bobId, carolId, oscarId] = await getRootIds([
alice,
bob,
carol,
oscar,
])
await run(
'start tribes',
Promise.all(peers.map((peer) => peer.tribes2.start()))
)

const group = await run('alice creates a group', alice.tribes2.create({}))

await run('(sync dm feeds)', replicate(alice, bob, carol, oscar))

await run(
'alice invites bob, carol, oscar',
alice.tribes2.addMembers(group.id, [bobId, carolId, oscarId], {})
)

await run('(sync dm feeds)', replicate(alice, bob, carol, oscar))

await run(
'others accept invites',
Promise.all([
bob.tribes2.acceptInvite(group.id),
carol.tribes2.acceptInvite(group.id),
oscar.tribes2.acceptInvite(group.id),
])
)
// </setup>

await Promise.all([
run(
'alice excludes carol, oscar',
alice.tribes2.excludeMembers(group.id, [carolId, oscarId], {})
),
run(
'oscar excludes alice, bob',
oscar.tribes2.excludeMembers(group.id, [aliceId, bobId], {})
),
])

await run(
'(sync exclusions)',
Promise.all([
replicate(alice, bob),
replicate(oscar, carol),
replicate(alice, oscar),
])
)

const DELAY = 1000
console.log('sleep', DELAY) // eslint-disable-line
await p(setTimeout)(DELAY)

const aliceTips = await Epochs(alice).getTipEpochs(group.id)
const oscarTips = await Epochs(oscar).getTipEpochs(group.id)
t.equal(aliceTips.length, 1, 'alice sees only one tip')
t.equal(oscarTips.length, 1, 'oscar sees only one tip')

const [alicePreferred, bobPreferred, carolPreferred, oscarPreferred] =
await Promise.all(
peers.map((peer) => Epochs(peer).getPreferredEpoch(group.id))
)

t.deepEqual(alicePreferred, bobPreferred, 'alice and bob agree epoch')
t.deepEqual(carolPreferred, oscarPreferred, 'carol and oscar agree epoch')
t.notDeepEqual(alicePreferred, oscarPreferred, 'the group is split')

t.end()
})

0 comments on commit 2713f04

Please sign in to comment.