update to support msg-v2

This commit is contained in:
Andre Staltz 2023-05-26 13:54:32 +03:00
parent 37bdc344f3
commit 32c5b903b1
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
6 changed files with 212 additions and 110 deletions

View File

@ -1,5 +1,5 @@
const { BloomFilter } = require('bloom-filters')
const FeedV1 = require('ppppp-db/feed-v1')
const MsgV2 = require('ppppp-db/msg-v2')
const p = require('util').promisify
const { isEmptyRange, estimateMsgCount } = require('./range')
const { parseGoal } = require('./goal')
@ -31,7 +31,7 @@ class Algorithm {
let minDepth = Number.MAX_SAFE_INTEGER
let maxDepth = 0
for (const rec of this.#peer.db.records()) {
if (!rec.msg?.content) continue
if (!rec.msg?.data) continue
const tangles = rec.msg.metadata.tangles
if (rec.hash === rootMsgHash) {
minDepth = 0
@ -105,7 +105,7 @@ class Algorithm {
const filter = BloomFilter.create(2 * filterSize, 0.00001)
if (!isEmptyRange(range)) {
for (const msg of this.yieldMsgsIn(rootMsgHash, range)) {
filter.add('' + round + FeedV1.getMsgHash(msg))
filter.add('' + round + MsgV2.getMsgHash(msg))
}
}
for (const msgId of extraIds) {
@ -119,7 +119,7 @@ class Algorithm {
const remoteFilter = BloomFilter.fromJSON(remoteBloomJSON)
const missing = []
for (const msg of this.yieldMsgsIn(rootMsgHash, range)) {
const msgHash = FeedV1.getMsgHash(msg)
const msgHash = MsgV2.getMsgHash(msg)
if (!remoteFilter.has('' + round + msgHash)) {
missing.push(msgHash)
}
@ -161,16 +161,19 @@ class Algorithm {
}
async commit(rootMsgHash, newMsgs, goal, myWantRange) {
// Filter out contentful newMsgs that are not in my want-range
// Filter out dataful newMsgs that are not in my want-range
const [minWant, maxWant] = myWantRange
const validNewMsgs = newMsgs
.filter((msg) => {
if (!msg.content) return true // contentless messages are always valid
const depth = msg.metadata.tangles[rootMsgHash]?.depth ?? 0
if (depth === 0 && FeedV1.getMsgHash(msg) !== rootMsgHash) {
if (depth === 0 && MsgV2.getMsgHash(msg) !== rootMsgHash) {
return false // the rootMsg is the only acceptable depth-zero msg
}
return minWant <= depth && depth <= maxWant
if (!msg.data) {
return depth < minWant
} else {
return minWant <= depth && depth <= maxWant
}
})
.sort((a, b) => {
const aDepth = a.metadata.tangles[rootMsgHash]?.depth ?? 0
@ -178,14 +181,11 @@ class Algorithm {
return aDepth - bDepth
})
// Simulate adding this whole tangle, and check if it's valid
let err
if ((err = this.#peer.db.validateTangle(rootMsgHash, validNewMsgs))) {
throw err
}
// TODO: Simulate adding this whole tangle, and check if it's valid
// Add new messages TODO: optimize perf, avoiding await / try / catch
for (const msg of newMsgs) {
// Add new messages
// TODO: optimize perf, avoiding await / try / catch
for (const msg of validNewMsgs) {
try {
await p(this.#peer.db.add)(msg, rootMsgHash)
} catch {}
@ -222,7 +222,7 @@ class Algorithm {
const msg = this.#peer.db.get(msgHash)
if (!msg) continue
if (isErasable) {
msgs.push({ ...msg, content: null })
msgs.push({ ...msg, data: null })
} else {
msgs.push(msg)
}

View File

@ -1,6 +1,5 @@
const toPull = require('push-stream-to-pull-stream')
const pull = require('pull-stream')
const FeedV1 = require('ppppp-db/feed-v1')
const makeDebug = require('debug')
const getSeverity = require('ssb-network-errors')
const Algorithm = require('./algorithm')
@ -75,11 +74,6 @@ module.exports = {
goals.set(tangleId, goal)
}
function setFeedGoal(author, type, goal = 'all') {
const tangleId = FeedV1.getFeedRootHash(author, type)
goals.set(tangleId, goal)
}
function initiate() {
for (const stream of streams) {
stream.initiate()
@ -89,7 +83,6 @@ module.exports = {
return {
connect,
setGoal,
setFeedGoal,
initiate,
}
},

View File

@ -35,7 +35,7 @@
"devDependencies": {
"bs58": "^5.0.0",
"c8": "7",
"ppppp-db": "github:staltz/ppppp-db",
"ppppp-db": "github:staltz/ppppp-db#msgv2",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",
"rimraf": "^4.4.0",

View File

@ -4,7 +4,7 @@ const os = require('os')
const rimraf = require('rimraf')
const SecretStack = require('secret-stack')
const caps = require('ssb-caps')
const FeedV1 = require('ppppp-db/feed-v1')
const MsgV2 = require('ppppp-db/msg-v2')
const p = require('util').promisify
const Algorithm = require('../lib/algorithm')
const { generateKeypair } = require('./util')
@ -34,34 +34,45 @@ test('sync a feed with goal=all', async (t) => {
})
await alice.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
const carolKeys = generateKeypair('carol')
const carolGroupMsg0 = MsgV2.createGroup(carolKeys, 'carol')
const carolId = MsgV2.getMsgHash(carolGroupMsg0)
await p(alice.db.add)(carolGroupMsg0, carolId)
await p(bob.db.add)(carolGroupMsg0, carolId)
const carolMsgs = []
const carolID = carolKeys.id
const carolID_b58 = FeedV1.stripAuthor(carolID)
for (let i = 1; i <= 10; i++) {
const rec = await p(alice.db.create)({
const rec = await p(alice.db.feed.publish)({
group: carolId,
type: 'post',
content: { text: 'm' + i },
data: { text: 'm' + i },
keys: carolKeys,
})
carolMsgs.push(rec.msg)
}
t.pass('alice has msgs 1..10 from carol')
const carolRootHash = alice.db.getFeedRoot(carolID, 'post')
const carolRootMsg = alice.db.get(carolRootHash)
const carolPostsRootHash = alice.db.feed.getRoot(carolId, 'post')
const carolPostsRootMsg = alice.db.get(carolPostsRootHash)
await p(bob.db.add)(carolRootMsg, carolRootHash)
await p(bob.db.add)(carolPostsRootMsg, carolPostsRootHash)
for (let i = 0; i < 7; i++) {
await p(bob.db.add)(carolMsgs[i], carolRootHash)
await p(bob.db.add)(carolMsgs[i], carolPostsRootHash)
}
{
const arr = [...bob.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(
arr,
['m1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7'],
@ -69,8 +80,8 @@ test('sync a feed with goal=all', async (t) => {
)
}
bob.tangleSync.setGoal(carolRootHash, 'all')
alice.tangleSync.setGoal(carolRootHash, 'all')
bob.tangleSync.setGoal(carolPostsRootHash, 'all')
alice.tangleSync.setGoal(carolPostsRootHash, 'all')
const remoteAlice = await p(bob.connect)(alice.getAddress())
t.pass('bob connected to alice')
@ -81,8 +92,8 @@ test('sync a feed with goal=all', async (t) => {
{
const arr = [...bob.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(
arr,
['m1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm10'],
@ -110,34 +121,45 @@ test('sync a feed with goal=newest', async (t) => {
})
await alice.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
const carolKeys = generateKeypair('carol')
const carolGroupMsg0 = MsgV2.createGroup(carolKeys, 'carol')
const carolId = MsgV2.getMsgHash(carolGroupMsg0)
await p(alice.db.add)(carolGroupMsg0, carolId)
await p(bob.db.add)(carolGroupMsg0, carolId)
const carolMsgs = []
const carolID = carolKeys.id
const carolID_b58 = FeedV1.stripAuthor(carolID)
for (let i = 1; i <= 10; i++) {
const rec = await p(alice.db.create)({
const rec = await p(alice.db.feed.publish)({
group: carolId,
type: 'post',
content: { text: 'm' + i },
data: { text: 'm' + i },
keys: carolKeys,
})
carolMsgs.push(rec.msg)
}
t.pass('alice has msgs 1..10 from carol')
const carolRootHash = alice.db.getFeedRoot(carolID, 'post')
const carolRootMsg = alice.db.get(carolRootHash)
const carolPostsRootHash = alice.db.feed.getRoot(carolId, 'post')
const carolPostsRootMsg = alice.db.get(carolPostsRootHash)
await p(bob.db.add)(carolRootMsg, carolRootHash)
await p(bob.db.add)(carolPostsRootMsg, carolPostsRootHash)
for (let i = 0; i < 7; i++) {
await p(bob.db.add)(carolMsgs[i], carolRootHash)
await p(bob.db.add)(carolMsgs[i], carolPostsRootHash)
}
{
const arr = [...bob.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(
arr,
['m1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7'],
@ -145,8 +167,8 @@ test('sync a feed with goal=newest', async (t) => {
)
}
bob.tangleSync.setGoal(carolRootHash, 'newest-5')
alice.tangleSync.setGoal(carolRootHash, 'all')
bob.tangleSync.setGoal(carolPostsRootHash, 'newest-5')
alice.tangleSync.setGoal(carolPostsRootHash, 'all')
const remoteAlice = await p(bob.connect)(alice.getAddress())
t.pass('bob connected to alice')
@ -157,8 +179,8 @@ test('sync a feed with goal=newest', async (t) => {
{
const arr = [...bob.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(
arr,
['m6', 'm7', 'm8', 'm9', 'm10'],
@ -186,30 +208,41 @@ test('sync a feed with goal=newest but too far behind', async (t) => {
})
await alice.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
const carolKeys = generateKeypair('carol')
const carolGroupMsg0 = MsgV2.createGroup(carolKeys, 'carol')
const carolId = MsgV2.getMsgHash(carolGroupMsg0)
await p(alice.db.add)(carolGroupMsg0, carolId)
await p(bob.db.add)(carolGroupMsg0, carolId)
const carolMsgs = []
const carolID = carolKeys.id
const carolID_b58 = FeedV1.stripAuthor(carolID)
for (let i = 1; i <= 10; i++) {
const rec = await p(alice.db.create)({
const rec = await p(alice.db.feed.publish)({
group: carolId,
type: 'post',
content: { text: 'm' + i },
data: { text: 'm' + i },
keys: carolKeys,
})
carolMsgs.push(rec.msg)
}
const carolRootHash = alice.db.getFeedRoot(carolID, 'post')
const carolRootMsg = alice.db.get(carolRootHash)
const carolPostsRootHash = alice.db.feed.getRoot(carolId, 'post')
const carolPostsRootMsg = alice.db.get(carolPostsRootHash)
const algo = new Algorithm(alice)
await algo.pruneNewest(carolRootHash, 5)
await algo.pruneNewest(carolPostsRootHash, 5)
{
const arr = [...alice.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(
arr,
['m6', 'm7', 'm8', 'm9', 'm10'],
@ -217,20 +250,20 @@ test('sync a feed with goal=newest but too far behind', async (t) => {
)
}
await p(bob.db.add)(carolRootMsg, carolRootHash)
await p(bob.db.add)(carolPostsRootMsg, carolPostsRootHash)
for (let i = 0; i < 2; i++) {
await p(bob.db.add)(carolMsgs[i], carolRootHash)
await p(bob.db.add)(carolMsgs[i], carolPostsRootHash)
}
{
const arr = [...bob.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(arr, ['m1', 'm2'], 'bob has msgs 1..2 from carol')
}
alice.tangleSync.setFeedGoal(carolID, 'post', 'newest-5')
bob.tangleSync.setFeedGoal(carolID, 'post', 'newest-5')
alice.tangleSync.setGoal(carolPostsRootHash, 'newest-5')
bob.tangleSync.setGoal(carolPostsRootHash, 'newest-5')
const remoteAlice = await p(bob.connect)(alice.getAddress())
t.pass('bob connected to alice')
@ -241,8 +274,8 @@ test('sync a feed with goal=newest but too far behind', async (t) => {
{
const arr = [...bob.db.msgs()]
.filter((msg) => msg.metadata.who === carolID_b58 && msg.content)
.map((msg) => msg.content.text)
.filter((msg) => msg.metadata.group === carolId && msg.data)
.map((msg) => msg.data.text)
t.deepEquals(
arr,
['m6', 'm7', 'm8', 'm9', 'm10'],

View File

@ -4,6 +4,7 @@ const os = require('os')
const rimraf = require('rimraf')
const SecretStack = require('secret-stack')
const caps = require('ssb-caps')
const MsgV2 = require('ppppp-db/msg-v2')
const p = require('util').promisify
const { generateKeypair } = require('./util')
@ -18,7 +19,9 @@ const aliceKeys = generateKeypair('alice')
const bobKeys = generateKeypair('bob')
function getTexts(iter) {
return [...iter].filter((msg) => msg.content).map((msg) => msg.content.text)
return [...iter]
.filter((msg) => msg.metadata.group && msg.data)
.map((msg) => msg.data.text)
}
/*
@ -72,56 +75,80 @@ test('sync a thread where both peers have portions', async (t) => {
path: BOB_DIR,
})
await alice.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
const carolKeys = generateKeypair('carol')
const carolID = carolKeys.id
const carolGroupMsg0 = MsgV2.createGroup(carolKeys, 'carol')
const carolId = MsgV2.getMsgHash(carolGroupMsg0)
const daveKeys = generateKeypair('dave')
const daveID = daveKeys.id
const daveGroupMsg0 = MsgV2.createGroup(daveKeys, 'dave')
const daveId = MsgV2.getMsgHash(daveGroupMsg0)
await alice.db.loaded()
await bob.db.loaded()
// Alice knows Bob, Carol, and Dave
await p(alice.db.add)(bobGroupMsg0, bobId)
await p(alice.db.add)(carolGroupMsg0, carolId)
await p(alice.db.add)(daveGroupMsg0, daveId)
const startA = await p(alice.db.create)({
// Bob knows Alice, Carol, and Dave
await p(bob.db.add)(aliceGroupMsg0, aliceId)
await p(bob.db.add)(carolGroupMsg0, carolId)
await p(bob.db.add)(daveGroupMsg0, daveId)
const startA = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A' },
data: { text: 'A' },
keys: aliceKeys,
})
const rootHashA = alice.db.getFeedRoot(aliceKeys.id, 'post')
const rootHashA = alice.db.feed.getRoot(aliceId, 'post')
const rootMsgA = alice.db.get(rootHashA)
await p(bob.db.add)(rootMsgA, rootHashA)
await p(bob.db.add)(startA.msg, rootHashA)
const replyB1 = await p(bob.db.create)({
const replyB1 = await p(bob.db.feed.publish)({
group: bobId,
type: 'post',
content: { text: 'B1' },
data: { text: 'B1' },
tangles: [startA.hash],
keys: bobKeys,
})
const replyB2 = await p(bob.db.create)({
const replyB2 = await p(bob.db.feed.publish)({
group: bobId,
type: 'post',
content: { text: 'B2' },
data: { text: 'B2' },
tangles: [startA.hash],
keys: bobKeys,
})
const rootHashB = bob.db.getFeedRoot(bobKeys.id, 'post')
const rootHashB = bob.db.feed.getRoot(bobId, 'post')
const rootMsgB = bob.db.get(rootHashB)
await p(alice.db.add)(rootMsgB, rootHashB)
await p(alice.db.add)(replyB1.msg, rootHashB)
await p(alice.db.add)(replyB2.msg, rootHashB)
const replyC1 = await p(alice.db.create)({
const replyC1 = await p(alice.db.feed.publish)({
group: carolId,
type: 'post',
content: { text: 'C1' },
data: { text: 'C1' },
tangles: [startA.hash],
keys: carolKeys,
})
const replyD1 = await p(bob.db.create)({
const replyD1 = await p(bob.db.feed.publish)({
group: daveId,
type: 'post',
content: { text: 'D1' },
data: { text: 'D1' },
tangles: [startA.hash],
keys: daveKeys,
})
@ -180,24 +207,40 @@ test('sync a thread where initiator does not have the root', async (t) => {
})
await alice.db.loaded()
await bob.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
const rootA = await p(alice.db.create)({
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
// Alice knows Bob
await p(alice.db.add)(bobGroupMsg0, bobId)
// Bob knows Alice
await p(bob.db.add)(aliceGroupMsg0, aliceId)
const rootA = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A' },
data: { text: 'A' },
keys: aliceKeys,
})
const replyA1 = await p(alice.db.create)({
const replyA1 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A1' },
data: { text: 'A1' },
tangles: [rootA.hash],
keys: aliceKeys,
})
const replyA2 = await p(alice.db.create)({
const replyA2 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A2' },
data: { text: 'A2' },
tangles: [rootA.hash],
keys: aliceKeys,
})
@ -247,24 +290,40 @@ test('sync a thread where receiver does not have the root', async (t) => {
})
await alice.db.loaded()
await bob.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
const rootA = await p(alice.db.create)({
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
// Alice knows Bob
await p(alice.db.add)(bobGroupMsg0, bobId)
// Bob knows Alice
await p(bob.db.add)(aliceGroupMsg0, aliceId)
const rootA = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A' },
data: { text: 'A' },
keys: aliceKeys,
})
const replyA1 = await p(alice.db.create)({
const replyA1 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A1' },
data: { text: 'A1' },
tangles: [rootA.hash],
keys: aliceKeys,
})
const replyA2 = await p(alice.db.create)({
const replyA2 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A2' },
data: { text: 'A2' },
tangles: [rootA.hash],
keys: aliceKeys,
})
@ -313,31 +372,48 @@ test('sync a thread with reactions too', async (t) => {
})
await alice.db.loaded()
const aliceGroupMsg0 = MsgV2.createGroup(aliceKeys, 'alice')
const aliceId = MsgV2.getMsgHash(aliceGroupMsg0)
await p(alice.db.add)(aliceGroupMsg0, aliceId)
await bob.db.loaded()
const bobGroupMsg0 = MsgV2.createGroup(bobKeys, 'bob')
const bobId = MsgV2.getMsgHash(bobGroupMsg0)
await p(bob.db.add)(bobGroupMsg0, bobId)
const rootA = await p(alice.db.create)({
// Alice knows Bob
await p(alice.db.add)(bobGroupMsg0, bobId)
// Bob knows Alice
await p(bob.db.add)(aliceGroupMsg0, aliceId)
const rootA = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A' },
data: { text: 'A' },
keys: aliceKeys,
})
const replyA1 = await p(alice.db.create)({
const replyA1 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A1' },
data: { text: 'A1' },
tangles: [rootA.hash],
keys: aliceKeys,
})
const replyA2 = await p(alice.db.create)({
const replyA2 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'post',
content: { text: 'A2' },
data: { text: 'A2' },
tangles: [rootA.hash],
keys: aliceKeys,
})
const reactionA3 = await p(alice.db.create)({
const reactionA3 = await p(alice.db.feed.publish)({
group: aliceId,
type: 'reaction',
content: { text: 'yes', link: replyA1.hash },
data: { text: 'yes', link: replyA1.hash },
tangles: [rootA.hash, replyA1.hash],
keys: aliceKeys,
})

View File

@ -5,7 +5,7 @@ const base58 = require('bs58')
function generateKeypair(seed) {
const keys = ssbKeys.generate('ed25519', seed, 'buttwoo-v1')
const { data } = SSBURI.decompose(keys.id)
keys.id = `ppppp:feed/v1/${base58.encode(Buffer.from(data, 'base64'))}`
keys.id = base58.encode(Buffer.from(data, 'base64'))
return keys
}