Skip to content

Commit 2713f04

Browse files
authored
Merge pull request #141 from ssbc/4.7_split
4.7 split membership
2 parents 2a4f9c7 + df5954e commit 2713f04

File tree

2 files changed

+103
-13
lines changed

2 files changed

+103
-13
lines changed

lib/epochs/index.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -465,19 +465,16 @@ function BuildPreferredEpoch(ssb, groupId) {
465465
}
466466

467467
// case 4.6 - groups have overlapping membership, but disjoint
468-
else if (
469-
intersection(members0, members1).size > 0
470-
) {
468+
else if (intersection(members0, members1).size > 0) {
471469
// choose one for now,
472470
preferredEpoch = tieBreak(tips)
473471
// but also kick off resolution
474472
fixDisjointEpochsLater(ssb, groupId) // <<< DELAYED SIDE EFFECTS!
473+
} else {
474+
return cb(new Error('unknown membership case!'))
475475
}
476-
477476
// case 4.7 - disjoint membership (no overlap!)
478-
479-
// prettier-ignore
480-
else return cb(Error('Membership case not handled yet'))
477+
// in this case peers should not even know about 2 distinct memberships
481478
} else return cb(Error(`case of ${tips.length} tips not handled yet`))
482479

483480
if (preferredEpoch.members) delete preferredEpoch.members

test/lib/epochs.test.js

+99-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ function getRootIds(peers) {
1616
peers.map((peer) => p(peer.metafeeds.findOrCreate)())
1717
).then((feeds) => feeds.map((feed) => feed.id))
1818
}
19+
function closeAll(peers) {
20+
return Promise.all(peers.map((peer) => p(peer.close)(true)))
21+
}
1922

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

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

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

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

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

462465
const [alice, bob, carol, oscar] = peers
463466
const [bobId, carolId, oscarId] = await getRootIds([bob, carol, oscar])
@@ -481,6 +484,7 @@ test('lib/epochs (getPreferredEpoch - 4.6. overlapping membership)', async (t) =
481484
'others accept invites',
482485
Promise.all([
483486
bob.tribes2.acceptInvite(group.id),
487+
carol.tribes2.acceptInvite(group.id),
484488
oscar.tribes2.acceptInvite(group.id),
485489
])
486490
)
@@ -520,8 +524,97 @@ test('lib/epochs (getPreferredEpoch - 4.6. overlapping membership)', async (t) =
520524
t.end()
521525
})
522526

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

530+
// alice starts a group, adds bob, carol, oscar
531+
// simultaneously:
532+
// - alice excludes carol, oscar
533+
// - oscar excludes alice, bob
534+
//
535+
// the group split!
536+
537+
const run = Run(t)
538+
539+
// <setup>
540+
const peers = [
541+
Server({ name: 'alice' }),
542+
Server({ name: 'bob' }),
543+
Server({ name: 'carol' }),
544+
Server({ name: 'oscar' }),
545+
]
546+
t.teardown(async () => await closeAll(peers))
547+
548+
const [alice, bob, carol, oscar] = peers
549+
const [aliceId, bobId, carolId, oscarId] = await getRootIds([
550+
alice,
551+
bob,
552+
carol,
553+
oscar,
554+
])
555+
await run(
556+
'start tribes',
557+
Promise.all(peers.map((peer) => peer.tribes2.start()))
558+
)
559+
560+
const group = await run('alice creates a group', alice.tribes2.create({}))
561+
562+
await run('(sync dm feeds)', replicate(alice, bob, carol, oscar))
563+
564+
await run(
565+
'alice invites bob, carol, oscar',
566+
alice.tribes2.addMembers(group.id, [bobId, carolId, oscarId], {})
567+
)
568+
569+
await run('(sync dm feeds)', replicate(alice, bob, carol, oscar))
570+
571+
await run(
572+
'others accept invites',
573+
Promise.all([
574+
bob.tribes2.acceptInvite(group.id),
575+
carol.tribes2.acceptInvite(group.id),
576+
oscar.tribes2.acceptInvite(group.id),
577+
])
578+
)
579+
// </setup>
580+
581+
await Promise.all([
582+
run(
583+
'alice excludes carol, oscar',
584+
alice.tribes2.excludeMembers(group.id, [carolId, oscarId], {})
585+
),
586+
run(
587+
'oscar excludes alice, bob',
588+
oscar.tribes2.excludeMembers(group.id, [aliceId, bobId], {})
589+
),
590+
])
591+
592+
await run(
593+
'(sync exclusions)',
594+
Promise.all([
595+
replicate(alice, bob),
596+
replicate(oscar, carol),
597+
replicate(alice, oscar),
598+
])
599+
)
600+
601+
const DELAY = 1000
602+
console.log('sleep', DELAY) // eslint-disable-line
603+
await p(setTimeout)(DELAY)
604+
605+
const aliceTips = await Epochs(alice).getTipEpochs(group.id)
606+
const oscarTips = await Epochs(oscar).getTipEpochs(group.id)
607+
t.equal(aliceTips.length, 1, 'alice sees only one tip')
608+
t.equal(oscarTips.length, 1, 'oscar sees only one tip')
609+
610+
const [alicePreferred, bobPreferred, carolPreferred, oscarPreferred] =
611+
await Promise.all(
612+
peers.map((peer) => Epochs(peer).getPreferredEpoch(group.id))
613+
)
614+
615+
t.deepEqual(alicePreferred, bobPreferred, 'alice and bob agree epoch')
616+
t.deepEqual(carolPreferred, oscarPreferred, 'carol and oscar agree epoch')
617+
t.notDeepEqual(alicePreferred, oscarPreferred, 'the group is split')
618+
526619
t.end()
527620
})

0 commit comments

Comments
 (0)