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

Adapt listMembers to exclusion and test it #95

Merged
merged 38 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fe81368
Add failing listMembers exclusion test and getPickedEpoch
Powersource Apr 18, 2023
fe01c0e
Use epoch functions to fix listMembers
Powersource Apr 19, 2023
6350e08
Remove .only
Powersource Apr 19, 2023
1c5ee7b
Move listMembers exclude tests to listMembers file
Powersource Apr 19, 2023
78f2931
Start to integrate getGroupInfoUpdates
Powersource Apr 20, 2023
23f87f6
Merge branch 'master' of github.com:ssbc/ssb-tribes2 into listmembers…
Powersource Apr 20, 2023
df4556d
Use new box2 version
Powersource Apr 21, 2023
362fa2d
Make getPickedEpoch a stream
Powersource Apr 22, 2023
f54684d
Try to make live listMembers work
Powersource Apr 22, 2023
929c279
Use new getMemberUpdates stream in listMembers instead
Powersource Apr 24, 2023
d1ce6c1
Fix getting empty excluded group object
Powersource Apr 24, 2023
3482198
Test listMembers live with exclude
Powersource Apr 24, 2023
d37c83a
Update listMembers readme for live exclude
Powersource Apr 24, 2023
c6ec4ec
Remove comment
Powersource Apr 24, 2023
bcea87e
Remove .only
Powersource Apr 24, 2023
4d7f63f
Merge branch 'master' of github.com:ssbc/ssb-tribes2 into listmembers…
Powersource May 2, 2023
68b262d
Test adding person to new epoch and checking live list
Powersource May 2, 2023
e309902
Fix small review suggestions
Powersource May 2, 2023
4758a34
Stop live stream in stream to fix switching between epochs
Powersource May 2, 2023
19c2b19
refactor epochs to consolidate getPreferredEpoch
mixmix May 3, 2023
145f853
tests passing
mixmix May 3, 2023
3c75122
Merge branch 'master' of github.com:ssbc/ssb-tribes2 into refactor_epoch
mixmix May 10, 2023
b68fc0f
Merge branch 'master' of github.com:ssbc/ssb-tribes2 into listmembers…
mixmix May 10, 2023
5288576
updates listMembers output, README
mixmix May 10, 2023
366dbf5
listen for new epochs
mixmix May 10, 2023
70cbecf
remove logs
mixmix May 10, 2023
fa49579
Merge branch 'listmembers-exclusion' of github.com:ssbc/ssb-tribes2 i…
mixmix May 10, 2023
833ad63
only log listener errors if ssb is still "open"
mixmix May 10, 2023
850e1c2
add err handler on listener drain
mixmix May 10, 2023
1bd9446
add timeout on bob close
mixmix May 10, 2023
3b6d921
extract ./listeners
mixmix May 11, 2023
430cae7
Merge pull request #104 from ssbc/refactor_epoch
mixmix May 11, 2023
25bdbce
Apply suggestions from code review
mixmix May 11, 2023
a67f6ee
test fixes: safer ssb.close, explicit t.end
mixmix May 11, 2023
bbc964d
Merge branch 'listmembers-exclusion' of github.com:ssbc/ssb-tribes2 i…
mixmix May 11, 2023
d613a48
preferredEpoch (live) now emits on membership updates
mixmix May 11, 2023
5d451ba
Merge pull request #112 from ssbc/listmembers-exclusions-fixes
Powersource May 11, 2023
2fd0c34
Update box2
Powersource May 11, 2023
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
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = {
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaVersion: 2020,
ecmaVersion: 2021,
sourceType: 'module',
},
rules: {
Expand Down
40 changes: 12 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const buildGroupId = require('./lib/build-group-id')
const addTangles = require('./lib/tangles/add-tangles')
const publishAndPrune = require('./lib/prune-publish')
const MetaFeedHelpers = require('./lib/meta-feed-helpers')
const Epochs = require('./lib/epochs')
const { groupRecp } = require('./lib/operators')
// const Epochs = require('./lib/epochs')

module.exports = {
name: 'tribes2',
Expand All @@ -60,7 +60,7 @@ module.exports = {
findOrCreateGroupWithoutMembers,
getRootFeedIdFromMsgId,
} = MetaFeedHelpers(ssb)
// const { getEpochs } = Epochs(ssb)
const { getPickedEpoch, getMembers } = Epochs(ssb)

function create(opts = {}, cb) {
if (cb === undefined) return promisify(create)(opts)
Expand Down Expand Up @@ -319,35 +319,21 @@ module.exports = {
}

function listMembers(groupId, opts = {}) {
const deferedSource = pullDefer.source()
const deferredSource = pullDefer.source()

get(groupId, (err, group) => {
// prettier-ignore
if (err) return deferedSource.abort(clarify(err, 'Failed to get group info when listing members'))
getPickedEpoch(groupId, (err, pickedEpoch) => {
// prettier-ignore
if (group.excluded) return deferedSource.abort( new Error("We're excluded from this group, can't list members"))
if (err) return deferredSource.abort(clarify(err, 'todo'))

const source = pull(
ssb.db.query(
where(
and(
isDecrypted('box2'),
type('group/add-member'),
groupRecp(groupId)
)
),
opts.live ? live({ old: true }) : null,
toPullStream()
),
pull.map((msg) => msg.value.content.recps.slice(1)),
pull.flatten(),
pull.unique()
)
getMembers(pickedEpoch.id, (err, pickedEpochMembers) => {
// prettier-ignore
if (err) return deferredSource.abort(clarify(err, 'todo'))

deferedSource.resolve(source)
deferredSource.resolve(pull.values(pickedEpochMembers.members))
})
})

return deferedSource
return deferredSource
}

function listInvites() {
Expand Down Expand Up @@ -528,9 +514,7 @@ module.exports = {
paraMap((msg, cb) => {
pull(
ssb.box2.listGroupIds(),
pull.filter((groupId) =>
groupId === msg.value.content.recps[0]
),
pull.filter((groupId) => groupId === msg.value.content.recps[0]),
pull.take(1),
pull.collect((err, groupIds) => {
// prettier-ignore
Expand Down
67 changes: 48 additions & 19 deletions lib/epochs.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,36 @@ module.exports = function Epochs(ssb) {
})
},

/**
* Get the epoch matching the currently picked write key for the group.
*/
getPickedEpoch(groupId, opts) {
mixmix marked this conversation as resolved.
Show resolved Hide resolved
return pull(
opts?.live
? ssb.box2.getGroupInfoUpdates(groupId)
: pull(ssb.box2.getGroupInfoUpdates(groupId), pull.take(1))
//pull.compareToTheLastOne?isTheWriteKeyDifferent?
)

ssb.box2.getGroupInfo(groupId, (err, info) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to get group info for ' + groupId))

// prettier-ignore
if (info.excluded) return cb(new Error("Can't get picked epoch for group we're excluded from"))

getEpochs(groupId, (err, epochs) => {
// prettier-ignore
if (err) return cb(clarify(err, 'todo'))

cb(
null,
epochs.find((epoch) => epoch.epochKey.equals(info.writeKey.key))
)
})
})
},

getMembers(epochRootId, cb) {
if (cb === undefined) return p(this.getMembers)(epochRootId)

Expand All @@ -111,8 +141,8 @@ module.exports = function Epochs(ssb) {
if (cb === undefined) return p(this.getMissingMembers)(groupId)

reduceEpochs(ssb, groupId, allGetters, (err, reduce) => {
if (err)
return cb(clarify(err, 'Failed to resolve epoch @tangle/reduce'))
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to resolve epoch @tangle/reduce'))

if (reduce.graph.connectedNodes.length === 1) return cb(null, [])
// INFO if there is only one connectedNode, there is only a
Expand Down Expand Up @@ -160,7 +190,6 @@ module.exports = function Epochs(ssb) {
throw Error('tieBreak requires an Array of epochs')

const keys = epochs.map((epoch) => epoch.epochKey.toString('hex')).sort()
console.log(keys)

const winningKey = Buffer.from(keys[0], 'hex')

Expand Down Expand Up @@ -188,28 +217,24 @@ function reduceEpochs(ssb, groupId, getters = {}, cb) {
// and nodes under `resolve.graph` (which is a @tangle/graph object)

ssb.box2.getGroupInfo(groupId, (err, info) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to get group info for ' + groupId))

// Fetch the tangle root
ssb.db.get(info.root, (err, rootVal) => {
if (err)
return cb(
clarify(err, 'Failed to load group root with id ' + info.root)
)
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to load group root with id ' + info.root))

if (!isInitRoot(rootVal))
return cb(
clarify(
new Error(isInitRoot.string),
'Malformed group/init root message'
)
)
// prettier-ignore
return cb(clarify(new Error(isInitRoot.string), 'Malformed group/init root message'))

const root = { key: info.root, value: rootVal }

// Fetch the tangle updates
getTangleUpdates(ssb, 'epoch', info.root, (err, updates) => {
if (err)
return cb(clarify(err, 'Failed to updates of group epoch tangle'))
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to updates of group epoch tangle'))

// Take each root/update and build an epoch "node" for our tangle
pull(
Expand All @@ -230,22 +255,24 @@ function reduceEpochs(ssb, groupId, getters = {}, cb) {
pull.values(Object.entries(getters)),
pull.asyncMap(([fieldName, getter], cb) => {
getter(msg, (err, fieldData) => {
if (err)
return cb(clarify(err, 'Failed to get epoch ' + fieldName))
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to get epoch ' + fieldName))

epochData[fieldName] = fieldData
cb(null)
})
}),
pull.collect((err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to collect epoch data'))

cb(null, node)
})
)
}),
pull.collect((err, nodes) => {
if (err)
return cb(clarify(err, 'Failure collecting epoch messages'))
// prettier-ignore
if (err) return cb(clarify(err, 'Failure collecting epoch messages'))

// Finally reduce all of these epoch nodes with a tangle
const reduce = new Reduce(strategy, { nodes })
Expand Down Expand Up @@ -276,7 +303,9 @@ function getMembers(ssb, epochRootId, cb) {
else excludes.forEach((feedId) => toExclude.add(feedId))
},
(err) => {
// prettier-ignore
if (err) return cb(clarify(err, 'Failed to resolve epoch membership'))

cb(null, {
added: [...added],
toExclude: [...toExclude],
Expand Down
1 change: 1 addition & 0 deletions lib/tangles/get-tangle-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function getTangleRoot(server, tangle, groupId, cb) {
return cb(new Error(`get-tangle: unknown groupId ${groupId}`))
}

// TODO: use getPickedEpoch?
Powersource marked this conversation as resolved.
Show resolved Hide resolved
if (tangle === 'members') {
// we find the id of the init msg for the current epoch
pull(
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"pull-stream": "^3.7.0",
"set.prototype.difference": "^1.0.2",
"ssb-bfe": "^3.7.0",
"ssb-box2": "^6.1.0",
"ssb-box2": "ssbc/ssb-box2#getgroupinfoupdates",
"ssb-crut": "^4.6.2",
"ssb-db2": "^6.3.3",
"ssb-meta-feeds": "^0.39.0",
Expand Down
12 changes: 0 additions & 12 deletions test/exclude-members.test.js
Powersource marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -271,18 +271,6 @@ test('Verify that you actually get excluded from a group', async (t) => {
const invites = await pull(bob.tribes2.listInvites(), pull.collectAsPromise())
t.deepEquals(invites, [], 'Bob has no invites')

await pull(bob.tribes2.listMembers(groupId), pull.collectAsPromise())
.then(() =>
t.fail(
"Bob didn't get an error when trying to list members of the group he's excluded from"
)
)
.catch(() =>
t.pass(
"Bob gets an error when trying to list members of the group he's excluded from"
)
)

Powersource marked this conversation as resolved.
Show resolved Hide resolved
await Promise.all([p(alice.close)(true), p(bob.close)(true)])
})

Expand Down
107 changes: 107 additions & 0 deletions test/list-members.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,110 @@ test('live list members', async (t) => {

await Promise.all([p(alice.close)(true), p(bob.close)(true)])
})

test('listMembers works with exclusion', async (t) => {
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 Promise.all([
alice.tribes2.start(),
bob.tribes2.start(),
carol.tribes2.start(),
]).then(() => t.pass('tribes2 started for everyone'))

const [aliceRoot, bobRoot, carolRoot] = await Promise.all([
p(alice.metafeeds.findOrCreate)(),
p(bob.metafeeds.findOrCreate)(),
p(carol.metafeeds.findOrCreate)(),
])

await Promise.all([
replicate(alice, bob),
replicate(alice, carol),
replicate(bob, carol),
]).then(() => t.pass('everyone replicates their trees'))

const { id: groupId } = 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 bob.tribes2.acceptInvite(groupId)
await carol.tribes2.acceptInvite(groupId)

await replicate(alice, bob)
await replicate(alice, carol)

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, bob)
await replicate(alice, carol)

const aliceMembers = await pull(
alice.tribes2.listMembers(groupId),
pull.collectAsPromise()
)
t.deepEquals(
aliceMembers.sort(),
[aliceRoot.id, carolRoot.id].sort(),
'alice gets the correct members list'
)

const carolMembers = await pull(
carol.tribes2.listMembers(groupId),
pull.collectAsPromise()
)
t.deepEquals(
carolMembers.sort(),
[aliceRoot.id, carolRoot.id].sort(),
'carol gets the correct members list'
)

await pull(bob.tribes2.listMembers(groupId), pull.collectAsPromise())
.then(() =>
t.fail(
"Bob didn't get an error when trying to list members of the group he's excluded from"
)
)
.catch(() =>
t.pass(
"Bob gets an error when trying to list members of the group he's excluded from"
)
)

await p(alice.close)(true)
await p(bob.close)(true)
await p(carol.close)(true)
Powersource marked this conversation as resolved.
Show resolved Hide resolved
})