From d2023b5bc076693d6d27f5c077c364a27eac21e6 Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Mon, 17 Apr 2023 16:25:43 +0300 Subject: [PATCH] Tangle class belongs to FeedV1 --- lib/feed-v1/index.js | 11 ++++++-- lib/{ => feed-v1}/tangle.js | 26 +++--------------- lib/feed-v1/validation.js | 2 +- lib/plugin.js | 32 ++++++++++++++++++---- lib/utils.js | 9 +----- test/add.test.js | 5 ++-- test/create.test.js | 10 +++---- test/feed-v1-create.test.js | 18 +++++++----- test/feed-v1-invalid-prev.test.js | 28 +++++++++++-------- test/feed-v1-lipmaa.test.js | 4 +-- test/feed-v1-tangles.test.js | 21 +++++++++----- test/feed-v1-validate.test.js | 13 +++++---- test/{tangle.test.js => getTangle.test.js} | 13 ++------- 13 files changed, 105 insertions(+), 87 deletions(-) rename lib/{ => feed-v1}/tangle.js (85%) rename test/{tangle.test.js => getTangle.test.js} (90%) diff --git a/lib/feed-v1/index.js b/lib/feed-v1/index.js index 02ddf80..57d080b 100644 --- a/lib/feed-v1/index.js +++ b/lib/feed-v1/index.js @@ -16,8 +16,14 @@ const { validateBatch, validateMsgHash, } = require('./validation') -const { isEmptyObject } = require('../utils') -const Tangle = require('../tangle') +const Tangle = require('./tangle') + +function isEmptyObject(obj) { + for (const _key in obj) { + return false + } + return true +} /** * @typedef {Iterator & {values: () => Iterator}} MsgIter @@ -215,6 +221,7 @@ module.exports = { stripAuthor, toPlaintextBuffer, fromPlaintextBuffer, + Tangle, validate, validateBatch, } diff --git a/lib/tangle.js b/lib/feed-v1/tangle.js similarity index 85% rename from lib/tangle.js rename to lib/feed-v1/tangle.js index 1c98d0d..6de070d 100644 --- a/lib/tangle.js +++ b/lib/feed-v1/tangle.js @@ -1,9 +1,5 @@ /** - * @typedef {import("./plugin").Rec} Rec - */ - -/** - * @typedef {import("./feed-v1").Msg} Msg + * @typedef {import("./index").Msg} Msg */ function lipmaa(n) { @@ -80,16 +76,12 @@ class Tangle { #maxDepth /** - * * @param {string} rootHash - * @param {Iterable} recordsIter + * @param {Iterable} msgsIter */ - constructor(rootHash, recordsIter = []) { + constructor(rootHash) { this.#rootHash = rootHash this.#maxDepth = 0 - for (const rec of recordsIter) { - this.add(rec.hash, rec.msg) - } } add(msgHash, msg) { @@ -184,7 +176,7 @@ class Tangle { return { type, who } } - #shortestPathToRoot(msgHash) { + shortestPathToRoot(msgHash) { const path = [] let current = msgHash while (true) { @@ -207,16 +199,6 @@ class Tangle { return path } - getDeletablesAndErasables(msgHash) { - const erasables = this.#shortestPathToRoot(msgHash) - const sorted = this.topoSort() - const index = sorted.indexOf(msgHash) - const deletables = sorted.filter( - (msgHash, i) => i < index && !erasables.includes(msgHash) - ) - return { deletables, erasables } - } - getMaxDepth() { return this.#maxDepth } diff --git a/lib/feed-v1/validation.js b/lib/feed-v1/validation.js index 1df2006..ede7c70 100644 --- a/lib/feed-v1/validation.js +++ b/lib/feed-v1/validation.js @@ -1,7 +1,7 @@ const base58 = require('bs58') const ed25519 = require('ssb-keys/sodium') const stringify = require('fast-json-stable-stringify') -const Tangle = require('../tangle') +const Tangle = require('./tangle') function validateShape(msg) { if (!msg || typeof msg !== 'object') { diff --git a/lib/plugin.js b/lib/plugin.js index 7f941bb..3d059f6 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -4,7 +4,6 @@ const AAOL = require('async-append-only-log') const promisify = require('promisify-4loc') const Obz = require('obz') const FeedV1 = require('./feed-v1') -const Tangle = require('./tangle') const { ReadyGate } = require('./utils') const { decrypt } = require('./encryption') @@ -41,6 +40,29 @@ const { decrypt } = require('./encryption') * @typedef {RecPresent | RecDeleted} Rec */ +class DBTangle extends FeedV1.Tangle { + /** + * @param {string} rootHash + * @param {Iterable} recordsIter + */ + constructor(rootHash, recordsIter) { + super(rootHash) + for (const rec of recordsIter) { + this.add(rec.hash, rec.msg) + } + } + + getDeletablesAndErasables(msgHash) { + const erasables = this.shortestPathToRoot(msgHash) + const sorted = this.topoSort() + const index = sorted.indexOf(msgHash) + const deletables = sorted.filter( + (msgHash, i) => i < index && !erasables.includes(msgHash) + ) + return { deletables, erasables } + } +} + exports.name = 'db' exports.init = function initDB(peer, config) { @@ -150,7 +172,7 @@ exports.init = function initDB(peer, config) { function populateTangles(tangleIds) { const tangles = {} for (const tangleId of tangleIds) { - tangles[tangleId] ??= new Tangle(tangleId, records()) + tangles[tangleId] ??= new DBTangle(tangleId, records()) } return tangles } @@ -162,8 +184,8 @@ exports.init = function initDB(peer, config) { function add(msg, tangleRootHash, cb) { // TODO: optimize this. This may be slow if you're adding many msgs in a - // row, because it creates a new Map() each time. - const tangle = new Tangle(tangleRootHash, records()) + // row, because it creates a new Map() each time. Perhaps with QuickLRU + const tangle = new DBTangle(tangleRootHash, records()) const msgHash = FeedV1.getMsgHash(msg) FeedV1.validate(msg, tangle, msgHash, tangleRootHash, validationCB) @@ -293,7 +315,7 @@ exports.init = function initDB(peer, config) { } function getTangle(tangleId) { - return new Tangle(tangleId, records()) + return new DBTangle(tangleId, records()) } function* msgs() { diff --git a/lib/utils.js b/lib/utils.js index d2cb0ef..73aee3a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -18,11 +18,4 @@ class ReadyGate { } } -function isEmptyObject(obj) { - for (const _key in obj) { - return false - } - return true -} - -module.exports = { ReadyGate, isEmptyObject } +module.exports = { ReadyGate } diff --git a/test/add.test.js b/test/add.test.js index 3857961..2cc16d0 100644 --- a/test/add.test.js +++ b/test/add.test.js @@ -5,7 +5,6 @@ const rimraf = require('rimraf') const SecretStack = require('secret-stack') const caps = require('ssb-caps') const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const p = require('util').promisify const { generateKeypair } = require('./util') @@ -26,6 +25,8 @@ test('add()', async (t) => { const recRoot = await p(peer.db.add)(rootMsg, rootHash) t.equals(recRoot.msg.metadata.when, 0, 'root msg added') + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(recRoot.hash, recRoot.msg) const inputMsg = FeedV1.create({ keys, @@ -33,7 +34,7 @@ test('add()', async (t) => { type: 'post', content: { text: 'This is the first post!' }, tangles: { - [rootHash]: new Tangle(rootHash, [recRoot]), + [rootHash]: tangle, }, }) diff --git a/test/create.test.js b/test/create.test.js index 0043569..c04a684 100644 --- a/test/create.test.js +++ b/test/create.test.js @@ -6,7 +6,6 @@ const SecretStack = require('secret-stack') const caps = require('ssb-caps') const p = require('util').promisify const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') const DIR = path.join(os.tmpdir(), 'ppppp-db-create') @@ -67,16 +66,17 @@ test('create()', async (t) => { }) test('add() forked then create() merged', async (t) => { + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) + tangle.add(rec1.hash, rec1.msg) + const msg3 = FeedV1.create({ keys, when: Date.now(), type: 'post', content: { text: '3rd post forked from 1st' }, tangles: { - [rootHash]: new Tangle(rootHash, [ - { hash: rootHash, msg: rootMsg }, - rec1, - ]), + [rootHash]: tangle }, }) diff --git a/test/feed-v1-create.test.js b/test/feed-v1-create.test.js index 52be52c..5bf9301 100644 --- a/test/feed-v1-create.test.js +++ b/test/feed-v1-create.test.js @@ -1,6 +1,5 @@ const tape = require('tape') const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') let rootMsg = null @@ -26,12 +25,15 @@ tape('FeedV1.create()', (t) => { const content = { text: 'Hello world!' } const when = 1652037377204 + const tangle1 = new FeedV1.Tangle(rootHash) + tangle1.add(rootHash, rootMsg) + const msg1 = FeedV1.create({ keys, content, type: 'post', tangles: { - [rootHash]: new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]), + [rootHash]: tangle1, }, when, }) @@ -65,6 +67,10 @@ tape('FeedV1.create()', (t) => { 'getMsgId' ) + const tangle2 = new FeedV1.Tangle(rootHash) + tangle2.add(rootHash, rootMsg) + tangle2.add(msgHash1, msg1) + const content2 = { text: 'Ola mundo!' } const msg2 = FeedV1.create({ @@ -72,10 +78,7 @@ tape('FeedV1.create()', (t) => { content: content2, type: 'post', tangles: { - [rootHash]: new Tangle(rootHash, [ - { hash: rootHash, msg: rootMsg }, - { hash: msgHash1, msg: msg1 }, - ]), + [rootHash]: tangle2, }, when: when + 1, }) @@ -112,7 +115,8 @@ tape('FeedV1.create()', (t) => { tape('create() handles DAG tips correctly', (t) => { const keys = generateKeypair('alice') const when = 1652037377204 - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, diff --git a/test/feed-v1-invalid-prev.test.js b/test/feed-v1-invalid-prev.test.js index 596043e..173351d 100644 --- a/test/feed-v1-invalid-prev.test.js +++ b/test/feed-v1-invalid-prev.test.js @@ -1,7 +1,6 @@ const tape = require('tape') const base58 = require('bs58') const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') tape('invalid msg with non-array prev', (t) => { @@ -10,7 +9,8 @@ tape('invalid msg with non-array prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg = FeedV1.create({ keys, @@ -37,7 +37,8 @@ tape('invalid msg with bad prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, @@ -81,7 +82,8 @@ tape('invalid msg with URI in prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, @@ -127,7 +129,8 @@ tape('invalid msg with unknown prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, @@ -152,15 +155,16 @@ tape('invalid msg with unknown prev', (t) => { }) const unknownMsgHash = FeedV1.getMsgHash(unknownMsg) + const tangle2 = new FeedV1.Tangle(rootHash) + tangle2.add(rootHash, rootMsg) + tangle2.add(unknownMsgHash, unknownMsg) + const msg2 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: new Tangle(rootHash, [ - { hash: rootHash, msg: rootMsg }, - { hash: unknownMsgHash, msg: unknownMsg }, - ]), + [rootHash]: tangle2 }, when: 1652030002000, }) @@ -183,7 +187,8 @@ tape('invalid feed msg with a different who', (t) => { const rootMsg = FeedV1.createRoot(keysA, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const feedTangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const feedTangle = new FeedV1.Tangle(rootHash) + feedTangle.add(rootHash, rootMsg) const msg = FeedV1.create({ keys: keysB, @@ -207,7 +212,8 @@ tape('invalid feed msg with a different type', (t) => { const rootMsg = FeedV1.createRoot(keysA, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const feedTangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const feedTangle = new FeedV1.Tangle(rootHash) + feedTangle.add(rootHash, rootMsg) const msg = FeedV1.create({ keys: keysA, diff --git a/test/feed-v1-lipmaa.test.js b/test/feed-v1-lipmaa.test.js index 9057b98..6a4a347 100644 --- a/test/feed-v1-lipmaa.test.js +++ b/test/feed-v1-lipmaa.test.js @@ -1,6 +1,5 @@ const tape = require('tape') const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') tape('lipmaa prevs', (t) => { @@ -10,7 +9,8 @@ tape('lipmaa prevs', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, diff --git a/test/feed-v1-tangles.test.js b/test/feed-v1-tangles.test.js index 8d832eb..246f570 100644 --- a/test/feed-v1-tangles.test.js +++ b/test/feed-v1-tangles.test.js @@ -1,6 +1,5 @@ const tape = require('tape') const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') tape('simple multi-author tangle', (t) => { @@ -9,11 +8,13 @@ tape('simple multi-author tangle', (t) => { const rootMsgA = FeedV1.createRoot(keysA, 'post') const rootHashA = FeedV1.getMsgHash(rootMsgA) - const tangleA = new Tangle(rootHashA, [{ hash: rootHashA, msg: rootMsgA }]) + const tangleA = new FeedV1.Tangle(rootHashA) + tangleA.add(rootHashA, rootMsgA) const rootMsgB = FeedV1.createRoot(keysB, 'post') const rootHashB = FeedV1.getMsgHash(rootMsgB) - const tangleB = new Tangle(rootHashB, [{ hash: rootHashB, msg: rootMsgB }]) + const tangleB = new FeedV1.Tangle(rootHashB) + tangleB.add(rootHashB, rootMsgB) const msg1 = FeedV1.create({ keys: keysA, @@ -31,13 +32,16 @@ tape('simple multi-author tangle', (t) => { 'msg1 has only feed tangle' ) + const tangleX = new FeedV1.Tangle(msgHash1) + tangleX.add(msgHash1, msg1) + const msg2 = FeedV1.create({ keys: keysB, content: { text: 'Hello world!' }, type: 'post', tangles: { [rootHashB]: tangleB, - [msgHash1]: new Tangle(msgHash1, [{ hash: msgHash1, msg: msg1 }]), + [msgHash1]: tangleX, }, when: 1652030002000, }) @@ -73,11 +77,13 @@ tape('lipmaa in multi-author tangle', (t) => { const rootMsgA = FeedV1.createRoot(keysA, 'post') const rootHashA = FeedV1.getMsgHash(rootMsgA) - const tangleA = new Tangle(rootHashA, [{ hash: rootHashA, msg: rootMsgA }]) + const tangleA = new FeedV1.Tangle(rootHashA) + tangleA.add(rootHashA, rootMsgA) const rootMsgB = FeedV1.createRoot(keysB, 'post') const rootHashB = FeedV1.getMsgHash(rootMsgB) - const tangleB = new Tangle(rootHashB, [{ hash: rootHashB, msg: rootMsgB }]) + const tangleB = new FeedV1.Tangle(rootHashB) + tangleB.add(rootHashB, rootMsgB) const msg1 = FeedV1.create({ keys: keysA, @@ -90,7 +96,8 @@ tape('lipmaa in multi-author tangle', (t) => { }) const msgHash1 = FeedV1.getMsgHash(msg1) tangleA.add(msgHash1, msg1) - const tangleThread = new Tangle(msgHash1, [{ hash: msgHash1, msg: msg1 }]) + const tangleThread = new FeedV1.Tangle(msgHash1) + tangleThread.add(msgHash1, msg1) t.deepEquals( Object.keys(msg1.metadata.tangles), diff --git a/test/feed-v1-validate.test.js b/test/feed-v1-validate.test.js index 0916f2e..c6221ac 100644 --- a/test/feed-v1-validate.test.js +++ b/test/feed-v1-validate.test.js @@ -1,7 +1,6 @@ const tape = require('tape') const base58 = require('bs58') const FeedV1 = require('../lib/feed-v1') -const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') tape('validate root msg', (t) => { @@ -9,7 +8,8 @@ tape('validate root msg', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) FeedV1.validate(rootMsg, tangle, rootHash, rootHash, (err) => { if (err) console.log(err) @@ -23,7 +23,8 @@ tape('validate 2nd msg with existing root', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, @@ -49,7 +50,8 @@ tape('validate 2nd forked msg', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1A = FeedV1.create({ keys, @@ -88,7 +90,8 @@ tape('invalid msg with unknown previous', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) const msg1 = FeedV1.create({ keys, diff --git a/test/tangle.test.js b/test/getTangle.test.js similarity index 90% rename from test/tangle.test.js rename to test/getTangle.test.js index 3e310df..904297f 100644 --- a/test/tangle.test.js +++ b/test/getTangle.test.js @@ -6,13 +6,13 @@ const SecretStack = require('secret-stack') const caps = require('ssb-caps') const p = require('util').promisify const { generateKeypair } = require('./util') -const Tangle = require('../lib/tangle') const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle') rimraf.sync(DIR) let peer let rootPost, reply1Lo, reply1Hi, reply2A, reply3Lo, reply3Hi +let tangle test('setup', async (t) => { const keysA = generateKeypair('alice') const keysB = generateKeypair('bob') @@ -81,11 +81,11 @@ test('setup', async (t) => { ]) reply3Lo = reply3B.localeCompare(reply3C) < 0 ? reply3B : reply3C reply3Hi = reply3B.localeCompare(reply3C) < 0 ? reply3C : reply3B + + tangle = peer.db.getTangle(rootPost) }) test('Tangle.has', (t) => { - const tangle = new Tangle(rootPost, peer.db.records()) - t.true(tangle.has(rootPost), 'has rootPost') t.true(tangle.has(reply1Lo), 'has reply1Lo') t.true(tangle.has(reply1Hi), 'has reply1Hi') @@ -97,7 +97,6 @@ test('Tangle.has', (t) => { }) test('Tangle.getDepth', t=> { - const tangle = new Tangle(rootPost, peer.db.records()) t.equals(tangle.getDepth(rootPost), 0, 'depth of rootPost is 0') t.equals(tangle.getDepth(reply1Lo), 1, 'depth of reply1Lo is 1') t.equals(tangle.getDepth(reply1Hi), 1, 'depth of reply1Hi is 1') @@ -108,13 +107,11 @@ test('Tangle.getDepth', t=> { }) test('Tangle.getMaxDepth', t => { - const tangle = new Tangle(rootPost, peer.db.records()) t.equals(tangle.getMaxDepth(), 3, 'max depth is 3') t.end() }) test('Tangle.topoSort', (t) => { - const tangle = new Tangle(rootPost, peer.db.records()) const sorted = tangle.topoSort() t.deepEquals(sorted, [ @@ -129,7 +126,6 @@ test('Tangle.topoSort', (t) => { }) test('Tangle.getTips', (t) => { - const tangle = new Tangle(rootPost, peer.db.records()) const tips = tangle.getTips() t.equals(tips.size, 2, 'there are 2 tips') @@ -139,7 +135,6 @@ test('Tangle.getTips', (t) => { }) test('Tangle.getLipmaaSet', (t) => { - const tangle = new Tangle(rootPost, peer.db.records()) t.equals(tangle.getLipmaaSet(0).size, 0, 'lipmaa 0 (empty)') t.equals(tangle.getLipmaaSet(1).size, 1, 'lipmaa 1 (-1)') @@ -162,7 +157,6 @@ test('Tangle.getLipmaaSet', (t) => { }) test('Tangle.getDeletablesAndErasables basic', (t) => { - const tangle = new Tangle(rootPost, peer.db.records()) const { deletables, erasables } = tangle.getDeletablesAndErasables(reply2A) t.deepEquals(deletables, [reply1Hi], 'deletables') @@ -171,7 +165,6 @@ test('Tangle.getDeletablesAndErasables basic', (t) => { }) test('Tangle.getDeletablesAndErasables with lipmaa', (t) => { - const tangle = new Tangle(rootPost, peer.db.records()) const { deletables, erasables } = tangle.getDeletablesAndErasables(reply3Lo) t.deepEquals(deletables, [reply1Lo, reply1Hi, reply2A], 'deletables')