From 20ccc90880f8ad50fa545632130107aeb4824c5d Mon Sep 17 00:00:00 2001 From: mix irving Date: Thu, 18 May 2023 16:37:19 +1200 Subject: [PATCH] replace replicate-all --- test/add-member.test.js | 271 +++++++++++++------------------------- test/helpers/replicate.js | 33 ++++- test/helpers/testbot.js | 38 +++++- 3 files changed, 157 insertions(+), 185 deletions(-) diff --git a/test/add-member.test.js b/test/add-member.test.js index da47125..f9c1912 100644 --- a/test/add-member.test.js +++ b/test/add-member.test.js @@ -253,209 +253,127 @@ test('addMembers too many members', async (t) => { await p(alice.close)(true) }) -test('addMembers adds to all the tip epochs and gives keys to all the old epochs as well', async (t) => { +test.only('addMembers adds to all the tip epochs and gives keys to all the old epochs as well', async (t) => { // alice adds bob and carol // alice and bob remove carol at the same time, creating forked epochs // everyone still replicates and sees the fork // alice adds david to the group, and he should see both forks and the original epoch - const alice = Testbot({ - keys: ssbKeys.generate(null, 'alice'), - mfSeed: Buffer.from( - '000000000000000000000000000000000000000000000000000000000000a1ce', - 'hex' - ), - db2: {}, - }) - const bob = Testbot({ - keys: ssbKeys.generate(null, 'bob'), - mfSeed: Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000b0b', - 'hex' - ), - db2: {}, - }) - const carol = Testbot({ - keys: ssbKeys.generate(null, 'carol'), - mfSeed: Buffer.from( - '00000000000000000000000000000000000000000000000000000000000ca201', - 'hex' - ), - db2: {}, - }) - const david = Testbot({ - keys: ssbKeys.generate(null, 'david'), - mfSeed: Buffer.from( - '00000000000000000000000000000000000000000000000000000000000da71d', - 'hex' - ), - db2: {}, - }) - await Promise.all([ - alice.tribes2.start(), - bob.tribes2.start(), - carol.tribes2.start(), - david.tribes2.start(), - ]) - .then(() => t.pass('clients started')) - .catch((err) => t.error(err)) + const run = Run(t) + + const alice = Testbot({ name: 'alice', db2: {} }) + const bob = Testbot({ name: 'bob', db2: {} }) + const carol = Testbot({ name: 'carol', db2: {} }) + const david = Testbot({ name: 'david', db2: {} }) + + await run( + 'clients started', + Promise.all([ + alice.tribes2.start(), + bob.tribes2.start(), + carol.tribes2.start(), + david.tribes2.start(), + ]) + ) const [aliceRootId, bobRootId, carolRootId, davidRootId] = ( - await Promise.all( - [alice, bob, carol, david].map((peer) => p(peer.metafeeds.findOrCreate)()) + await run( + 'got peer roots', + Promise.all( + [alice, bob, carol, david].map((peer) => + p(peer.metafeeds.findOrCreate)() + ) + ) ) - .then((res) => { - t.pass('got peer roots') - return res - }) - .catch((err) => t.error(err)) ).map((root) => root.id) async function replicateAll() { - await p(setTimeout)(4000) + await p(setTimeout)(2000) - const fail = (err, a, b) => { - console.error(`Replication between ${a} and ${b} failed:`, err) - t.error(err) - } + await replicate(alice, bob, carol, david) + .then(() => t.pass('replicated all')) + .catch((err) => t.error(err, 'replicated all')) - await replicate(alice, bob) - .then(() => t.pass('replicated alice and bob')) - .catch((err) => fail(err, 'alice', 'bob')) - - await replicate(alice, carol) - .then(() => t.pass('replicated alice and carol')) - .catch((err) => fail(err, 'alice', 'carol')) - - await replicate(alice, david) - .then(() => t.pass('replicated alice and david')) - .catch((err) => fail(err, 'alice', 'david')) - - await replicate(bob, carol) - .then(() => t.pass('replicated bob and carol')) - .catch((err) => { - p(bob.metafeeds.printTree)(bobRootId, { id: true }) - p(bob.metafeeds.printTree)(aliceRootId, { id: true }) - p(carol.metafeeds.printTree)(carolRootId, { id: true }) - p(carol.metafeeds.printTree)(aliceRootId, { id: true }) - fail(err, 'bob', 'carol') - }) - - await replicate(carol, david) - .then(() => t.pass('replicated carol and david')) - .catch((err) => fail(err, 'carol', 'david')) - - await replicate(david, alice) - .then(() => t.pass('replicated david and alice')) - .catch((err) => fail(err, 'david', 'alice')) - - await p(setTimeout)(4000) + await p(setTimeout)(2000) } await replicateAll() - const { id: groupId, writeKey: firstEpochKey } = await alice.tribes2 - .create() - .then((res) => { - t.pass('alice created group') - return res - }) - .catch((err) => t.error(err)) + const { id: groupId, writeKey: firstEpochKey } = await run( + 'alice created group', + alice.tribes2.create() + ) const firstEpochSecret = firstEpochKey.key.toString('base64') - const { key: firstEpochPostId } = await alice.tribes2 - .publish({ + const { key: firstEpochPostId } = await run( + 'alice published in first epoch', + alice.tribes2.publish({ type: 'test', text: 'first post', recps: [groupId], }) - .then((res) => { - t.pass('alice published in first epoch') - return res - }) - .catch((err) => t.error(err)) + ) - await alice.tribes2 - .addMembers(groupId, [bobRootId, carolRootId]) - .then(() => t.pass('alice added bob and carol')) - .catch((err) => t.error(err)) + await run( + 'alice added bob and carol', + alice.tribes2.addMembers(groupId, [bobRootId, carolRootId]) + ) await replicateAll() - await bob.tribes2 - .acceptInvite(groupId) - .then(() => t.pass('bob accepted invite')) - .catch((err) => t.error(err)) + await run('bob accepted invite', bob.tribes2.acceptInvite(groupId)) - await Promise.all([ - alice.tribes2.excludeMembers(groupId, [carolRootId]), - bob.tribes2.excludeMembers(groupId, [carolRootId]), - ]) - .then(() => t.pass('alice and bob excluded carol')) - .catch((err) => t.error(err)) + await run( + 'alice and bob excluded carol', + Promise.all([ + alice.tribes2.excludeMembers(groupId, [carolRootId]), + bob.tribes2.excludeMembers(groupId, [carolRootId]), + ]) + ) - const { key: aliceForkPostId } = await alice.tribes2 - .publish({ + const { key: aliceForkPostId } = await run( + 'alice published in her fork', + alice.tribes2.publish({ type: 'test', text: 'alice fork post', recps: [groupId], }) - .then((res) => { - t.pass('alice published in her fork') - return res - }) - .catch((err) => t.error(err)) - const { writeKey: aliceForkKey } = await alice.tribes2 - .get(groupId) - .then((res) => { - t.pass('alice got info on her fork') - return res - }) - .catch((err) => t.error(err)) + ) + + const { writeKey: aliceForkKey } = await run( + 'alice got info on her fork', + alice.tribes2.get(groupId) + ) const aliceForkSecret = aliceForkKey.key.toString('base64') - const { key: bobForkPostId } = await bob.tribes2 - .publish({ + const { key: bobForkPostId } = await run( + 'bob posted in his fork', + bob.tribes2.publish({ type: 'test', text: 'bob fork post', recps: [groupId], }) - .then((res) => { - t.pass('bob posted in his fork') - return res - }) - .catch((err) => t.error(err)) - const { writeKey: bobForkKey } = await bob.tribes2 - .get(groupId) - .then((res) => { - t.pass('bob got info on his fork') - return res - }) - .catch((err) => t.error(err)) + ) + + const { writeKey: bobForkKey } = await run( + 'bob got info on his fork', + bob.tribes2.get(groupId) + ) const bobForkSecret = bobForkKey.key.toString('base64') await replicateAll() - const addDavid = await alice.tribes2 - .addMembers(groupId, [davidRootId]) - .then((res) => { - t.pass('david got added to the group by alice') - return res - }) - .catch((err) => t.error(err)) - + const addDavid = await run( + 'david got added to the group by alice', + alice.tribes2.addMembers(groupId, [davidRootId]) + ) t.equal(addDavid.length, 2, 'David got added to both forks') - const adds = await Promise.all( - addDavid.map((add) => p(alice.db.get)(add.key)) + const adds = await run( + 'alice got her additions of david', + Promise.all(addDavid.map((add) => p(alice.db.get)(add.key))) ) - .then((res) => { - t.pass('alice got her additions of david') - return res - }) - .catch((err) => t.error(err)) const addContents = adds.map((add) => add.content) - const addAliceFork = addContents.find( (content) => content.secret === aliceForkSecret ) @@ -484,45 +402,34 @@ test('addMembers adds to all the tip epochs and gives keys to all the old epochs "gave david the secret to the initial epoch, in the addition to bob's fork" ) - await replicate(alice, david) - .then(() => t.pass('replicated')) - .catch((err) => t.error(err)) + await run('replicated', replicate(alice, david)) - await david.tribes2 - .acceptInvite(groupId) - .then(() => t.pass('david accepted invite')) - .catch((err) => t.error(err)) + await run('david accepted invite', david.tribes2.acceptInvite(groupId)) - const bobForkMsg = await p(david.db.get)(bobForkPostId) - .then((res) => { - t.pass("david got bob's post in his fork") - return res - }) - .catch((err) => t.error(err)) + const bobForkMsg = await run( + "david got bob's post in his fork", + p(david.db.get)(bobForkPostId) + ) t.notEquals( typeof bobForkMsg.content, 'string', "david decrypted the msg in bob's fork" ) - const aliceForkMsg = await p(david.db.get)(aliceForkPostId) - .then((res) => { - t.pass("david got alice's post in her fork") - return res - }) - .catch((err) => t.error(err)) + const aliceForkMsg = await run( + "david got alice's post in her fork", + p(david.db.get)(aliceForkPostId) + ) t.notEquals( typeof aliceForkMsg.content, 'string', "david decrypted the msg in alice's fork" ) - const firstEpochMsg = await p(david.db.get)(firstEpochPostId) - .then((res) => { - t.pass("david got alice's post in the initial epoch") - return res - }) - .catch((err) => t.error(err)) + const firstEpochMsg = await run( + "david got alice's post in the initial epoch", + p(david.db.get)(firstEpochPostId) + ) t.notEquals( typeof firstEpochMsg.content, 'string', @@ -535,6 +442,6 @@ test('addMembers adds to all the tip epochs and gives keys to all the old epochs p(carol.close)(true), p(david.close)(true), ]) - .then(() => t.pass('clients got closed')) + .then(() => t.pass('clients close')) .catch((err) => t.error(err)) }) diff --git a/test/helpers/replicate.js b/test/helpers/replicate.js index 74cfd74..88abdcd 100644 --- a/test/helpers/replicate.js +++ b/test/helpers/replicate.js @@ -7,10 +7,38 @@ const pull = require('pull-stream') const pullMany = require('pull-many') const deepEqual = require('fast-deep-equal') +module.exports = async function replicate(...peers) { + if (peers.length === 1 && Array.isArray(peers[0])) peers = peers[0] + if (peers.length === 2) return replicatePair(...peers) + + return pull( + pull.values(peers), + pull.asyncMap((person1, cb) => { + pull( + pull.values(peers), + pull.asyncMap((person2, cb) => { + if (person1.id === person2.id) return cb(null, true) + + replicatePair(person1, person2) + .then((res) => cb(null, true)) + .catch((err) => cb(err)) + }), + pull.collect(cb) + ) + }), + pull.collectAsPromise() + ) +} + /** * Fully replicates person1's metafeed tree to person2 and vice versa */ -module.exports = async function replicate(person1, person2) { +async function replicatePair(person1, person2) { + // const start = Date.now() + // const ID = [person1, person2] + // .map(p => p.name || p.id.slice(0, 10)) + // .join('-') + // Establish a network connection const conn = await p(person1.connect)(person2.getAddress()) @@ -24,6 +52,9 @@ module.exports = async function replicate(person1, person2) { } await p(conn.close)(true).catch(console.error) + // const time = Date.now() - start + // const length = Math.max(Math.round(time / 100), 1) + // console.log(ID, Array(length).fill('▨').join(''), time + 'ms') } async function ebtReplicate(person1, person2) { diff --git a/test/helpers/testbot.js b/test/helpers/testbot.js index 564f5c2..89f3283 100644 --- a/test/helpers/testbot.js +++ b/test/helpers/testbot.js @@ -25,7 +25,10 @@ module.exports = function createSbot(opts = {}) { const dir = opts.path || `/tmp/ssb-tribes2-tests-${opts.name || count++}` if (opts.rimraf !== false) rimraf.sync(dir) - const keys = opts.keys || ssbKeys.loadOrCreateSync(path.join(dir, 'secret')) + const keys = + opts.keys || opts.name + ? ssbKeys.generate(null, opts.name) + : ssbKeys.loadOrCreateSync(path.join(dir, 'secret')) const stack = SecretStack({ caps: { shs } }) .use(require('ssb-db2/core')) @@ -51,11 +54,42 @@ module.exports = function createSbot(opts = {}) { writeTimeout: 10, }, metafeeds: { - seed: opts.mfSeed, + seed: opts.mfSeed || mfSeedFromName(opts.name), }, }) + sbot.name = opts.name sbot.ebt.registerFormat(bendyButtFormat) return sbot } + +function mfSeedFromName(name) { + if (!name) return + + switch (name) { + case 'alice': + return Buffer.from( + '000000000000000000000000000000000000000000000000000000000000a1ce', + 'hex' + ) + case 'bob': + return Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000b0b', + 'hex' + ) + case 'carol': + return Buffer.from( + '00000000000000000000000000000000000000000000000000000000000ca201', + 'hex' + ) + case 'david': + return Buffer.from( + '00000000000000000000000000000000000000000000000000000000000da71d', + 'hex' + ) + + default: + throw new Error('no mfSeed set up for ' + name) + } +}