Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

People not removed in an exclude should be able to post on the new epoch #78

Merged
merged 20 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@

name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]
on: push

jobs:
licenses:
Expand Down
79 changes: 76 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ module.exports = {
tangles: ['members'],
isValid: isExclude,
}
publish(excludeContent, excludeOpts, (err) => {
publish(excludeContent, excludeOpts, (err, excludeMsg) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to publish exclude msg'))

Expand Down Expand Up @@ -231,10 +231,11 @@ module.exports = {
// prettier-ignore
if (err) return cb(clarify(err, "Couldn't post init msg on new epoch when excluding members"))

addMembers(groupId, remainingMembers, {}, (err) => {
addMembers(groupId, remainingMembers, {}, (err, reMsg) => {
// prettier-ignore
if (err) return cb(clarify(err, "Couldn't re-add remaining members when excluding members"))
return cb()
//TODO: we probably don't want to return this exactly
return cb(null, { excludeMsg, reAddMsg: reMsg })
Powersource marked this conversation as resolved.
Show resolved Hide resolved
})
})
})
Expand Down Expand Up @@ -407,6 +408,78 @@ module.exports = {
if (err) return cb(clarify(err, 'Error finding or creating additions feed when starting ssb-tribes2'))
return cb()
})

// look for new epochs that we're added to
ssb.metafeeds.findOrCreate((err, myRoot) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))
Powersource marked this conversation as resolved.
Show resolved Hide resolved

pull(
ssb.db.query(
// TODO: does this output new stuff if we accept an invite to an old epoch and then find additions to newer epochs?
where(and(isDecrypted('box2'), type('group/add-member'))),
live({ old: true }),
toPullStream()
Powersource marked this conversation as resolved.
Show resolved Hide resolved
),
// groups/epochs we're added to
pull.filter((msg) => {
console.log('found an addition', myRoot.id, msg.value.content.recps)
Powersource marked this conversation as resolved.
Show resolved Hide resolved
return msg.value?.content?.recps?.includes(myRoot.id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return msg.value?.content?.recps?.includes(myRoot.id)
return isGroupAdd(msg) && msg.value.content.recps.includes(myRoot.id)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked against the spec in a filter before this so removed the ?s

}),
// to find new epochs we only check groups we've accepted the invite to
paraMap((msg, cb) => {
console.log('i am', myRoot.id)
console.log('found an addition of me', msg.value)
pull(
ssb.box2.listGroupIds(),
pull.collect((err, groupIds) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))

if (groupIds.includes(msg.value?.content?.recps?.[0])) {
return cb(null, msg)
} else {
return cb()
}
})
)
}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paraMap(fn) === pull.asyncMap(fn)
You need to add a "width" for it to be running in parallel e.g. run at most 4 at once:

Suggested change
}),
}, 4),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

pull.filter(Boolean),
pull.drain(
(msg) => {
console.log("addition was for a group i've already joined")
const groupId = msg.value?.content?.recps?.[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 we should get rid of this with isGroupAdd waaay further up in the stream.
Then we never have to do this oh... do we have this thing or not? That's the whole reason I got into JSON-Schema.
Like in the line below

const newKey = Buffer.from(msg.value?.content?.groupKey, 'base64')

if groupKey is not there this code is gonna blow up in your face

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


const newKey = Buffer.from(msg.value?.content?.groupKey, 'base64')
ssb.box2.addGroupInfo(groupId, { key: newKey }, (err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))

const newKeyPick = {
mixmix marked this conversation as resolved.
Show resolved Hide resolved
key: newKey,
scheme: keySchemes.private_group,
}
// TODO: naively guessing that this is the latest key for now
ssb.box2.pickGroupWriteKey(groupId, newKeyPick, (err) => {
Powersource marked this conversation as resolved.
Show resolved Hide resolved
// prettier-ignore
if (err) return cb(clarify(err, "todo"))

console.log('picked new key')

ssb.db.reindexEncrypted((err) => {
// prettier-ignore
if (err) cb(clarify(err, 'todo'))
})
})
})
},
(err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))
}
)
)
})
}

return {
Expand Down
140 changes: 140 additions & 0 deletions test/exclude-members.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { fromMessageSigil } = require('ssb-uri2')
const Testbot = require('./helpers/testbot')
const replicate = require('./helpers/replicate')
const countGroupFeeds = require('./helpers/count-group-feeds')
const pull = require('pull-stream')

test('add and remove a person, post on the new feed', async (t) => {
// Alice's feeds should look like
Expand Down Expand Up @@ -174,3 +175,142 @@ test('add and remove a person, post on the new feed', async (t) => {
await p(alice.close)(true)
await p(bob.close)(true)
})

test("If you're not the excluder nor the excludee then you should still be in the group and have access to the new epoch", async (t) => {
// alice creates the group. adds bob and carol. removes bob.
// bob gets added and is removed
// carol stays in the group
const alice = Testbot({
keys: ssbKeys.generate(null, 'alice'),
mfSeed: Buffer.from(
'000000000000000000000000000000000000000000000000000000000000a1ce',
'hex'
),
})
const bob = Testbot({
keys: ssbKeys.generate(null, 'bob'),
mfSeed: Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000b0b',
'hex'
),
})
const carol = Testbot({
keys: ssbKeys.generate(null, 'carol'),
mfSeed: Buffer.from(
'00000000000000000000000000000000000000000000000000000000000ca201',
'hex'
),
})

await alice.tribes2.start()
await bob.tribes2.start()
await carol.tribes2.start()
t.pass('tribes2 started for everyone')

await p(alice.metafeeds.findOrCreate)()
const bobRoot = await p(bob.metafeeds.findOrCreate)()
const carolRoot = await p(carol.metafeeds.findOrCreate)()

await replicate(alice, bob)
await replicate(alice, carol)
await replicate(bob, carol)
t.pass('everyone replicates their trees')

const { id: groupId, writeKey: writeKey1 } = await alice.tribes2
.create()
.catch((err) => t.error(err, 'alice failed to create group'))

await replicate(alice, carol)

await alice.tribes2
.addMembers(groupId, [bobRoot.id, carolRoot.id])
.catch((err) => t.error(err, 'add bob fail'))

await replicate(alice, carol)

await carol.tribes2.acceptInvite(groupId)

await replicate(alice, carol)

const {
value: { author: firstFeedId },
} = await carol.tribes2
.publish({
type: 'test',
text: 'first post',
recps: [groupId],
})
.catch((err) => t.error(err, 'carol failed to publish on first feed'))
if (firstFeedId) t.pass('carol posted first post')

const { reAddMsg } = await alice.tribes2
.excludeMembers(groupId, [bobRoot.id])
.then((res) => {
t.pass('alice excluded bob')
return res
})
.catch((err) => t.error(err, 'remove member fail'))

await replicate(alice, carol).catch(t.error)

// TODO: maybe remove?
await p(setTimeout)(10000)
Powersource marked this conversation as resolved.
Show resolved Hide resolved

const carolHasReAddMsg = await p(carol.db.getMsg)(reAddMsg.key)

console.log('carol has readd', carolHasReAddMsg)

const {
value: { author: secondFeedId },
} = await carol.tribes2
.publish({
type: 'test',
text: 'second post',
recps: [groupId],
})
.catch(t.fail)
if (secondFeedId) t.pass('carol posted second post')

t.notEquals(
secondFeedId,
firstFeedId,
'feed for second publish is different to first publish'
)
Powersource marked this conversation as resolved.
Show resolved Hide resolved

const { writeKey: writeKey2 } = await carol.tribes2.get(groupId)

const branches = await pull(
carol.metafeeds.branchStream({
root: carolRoot.id,
old: true,
live: false,
}),
pull.collectAsPromise()
)

console.log('carol branches', branches)

const groupFeedPurposes = branches
.filter((branch) => branch.length === 4)
.map((branch) => branch[3])
.filter((feed) => feed.recps && feed.purpose.length === 44)
.map((feed) => feed.purpose)

console.log({
groupFeedPurposes,
key1: writeKey1.key.toString('base64'),
key2: writeKey2.key.toString('base64'),
})
t.true(
groupFeedPurposes.includes(writeKey1.key.toString('base64')),
'Carol has a feed for the old key'
)
t.true(
groupFeedPurposes.includes(writeKey2.key.toString('base64')),
'Carol has a feed for the new key'
)

await p(alice.close)(true)
await p(bob.close)(true)
await p(carol.close)(true)
})
2 changes: 2 additions & 0 deletions test/helpers/replicate.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ module.exports = async function replicate(person1, person2) {
await retryUntil(async () => {
const newClock1 = await p(person1.getVectorClock)()
const newClock2 = await p(person2.getVectorClock)()
//console.log('newClock1', newClock1)
//console.log('newClock2', newClock2)
return deepEqual(newClock1, newClock2)
})

Expand Down