diff --git a/lib/feed-v1/index.js b/lib/feed-v1/index.js index bc63d7c..aebe136 100644 --- a/lib/feed-v1/index.js +++ b/lib/feed-v1/index.js @@ -5,6 +5,7 @@ const stringify = require('fast-json-stable-stringify') const ed25519 = require('ssb-keys/sodium') const base58 = require('bs58') +const union = require('set.prototype.union') const { stripAuthor } = require('./strip') const { getMsgId, getMsgHash } = require('./get-msg-id') const representContent = require('./represent-content') @@ -15,6 +16,7 @@ const { validateBatch, validateMsgHash, } = require('./validation') +const Tangle = require('../tangle') /** * @typedef {Iterator & {values: () => Iterator}} MsgIter @@ -51,7 +53,7 @@ const { * @property {string} type * @property {number} when * @property {Keys} keys - * @property {Record} tangles + * @property {Record} tangles */ /** @@ -93,122 +95,6 @@ function toPlaintextBuffer(opts) { return Buffer.from(stringify(opts.content), 'utf8') } -function calculateDepth(existing, tangleId = null) { - let max = -1 - for (const msg of existing.values()) { - const depth = msg.metadata.tangles[tangleId]?.depth ?? 0 - if (depth > max) { - max = depth - } - } - return max + 1 -} - -function lipmaa(n) { - let m = 1 - let po3 = 3 - let u = n - - // find k such that (3^k - 1)/2 >= n - while (m < n) { - po3 *= 3 - m = (po3 - 1) / 2 - } - - // find longest possible backjump - po3 /= 3 - if (m !== n) { - while (u !== 0) { - m = (po3 - 1) / 2 - po3 /= 3 - u %= m - } - - if (m !== po3) { - po3 = m - } - } - - return n - po3 -} - -function determineTips(existing, tangleId = null) { - const tips = new Set() - for (const msg of existing.values()) { - tips.add(getMsgHash(msg)) - } - - for (const msg of existing.values()) { - const prev = msg.metadata.tangles[tangleId]?.prev ?? [] - for (const p of prev) { - tips.delete(p) - } - } - return tips -} - -function calculatePrev(existing, depth, lipmaaDepth, tangleId = null) { - const prev = [] - const tips = determineTips(existing, tangleId) - for (const msg of existing.values()) { - const msgDepth = msg.metadata.tangles[tangleId]?.depth ?? 0 - const msgHash = getMsgHash(msg) - if ( - msgDepth === depth - 1 || - msgDepth === lipmaaDepth || - tips.has(msgHash) - ) { - prev.push(msgHash) - } - } - return prev -} - -/** - * @param {MsgIter} existing - * @param {string} tangleId - * @returns - */ -function prevalidateExisting(existing, tangleId) { - if (!existing?.[Symbol.iterator]) { - // prettier-ignore - return new Error(`existing must be an iterator, but got ${typeof existing}`) - } - if (typeof existing?.values !== 'function') { - // prettier-ignore - return new Error(`existing must be a Map, Set, or Array, but got ${existing}`) - } - if (!tangleId) { - // prettier-ignore - return new Error(`tangleId must be a string, but got ${typeof tangleId}`) - } - let isEmpty = true - let hasDepthZeroMsg = false - for (const p of existing.values()) { - isEmpty = false - if (!p.metadata) { - // prettier-ignore - return new Error(`existing must contain messages, but got ${typeof p}`) - } - - if (!p.metadata.tangles[tangleId] && getMsgHash(p) === tangleId) { - if (hasDepthZeroMsg) { - // prettier-ignore - return new Error(`existing must contain only 1 message with depth 0`) - } else { - hasDepthZeroMsg = true - } - } else if (!p.metadata.tangles[tangleId]) { - // prettier-ignore - return new Error(`existing must refer to the tangleId ${tangleId}`) - } - } - if (!isEmpty && !hasDepthZeroMsg) { - // prettier-ignore - return new Error(`opts.existing must contain the message with depth 0`) - } -} - /** * @param {CreateOpts} opts * @returns {Msg} @@ -224,12 +110,11 @@ function create(opts) { if (opts.tangles) { for (const rootId in opts.tangles) { if ((err = validateMsgHash(rootId))) throw err - const existing = opts.tangles[rootId] - if ((err = prevalidateExisting(existing, rootId))) throw err - - const depth = calculateDepth(existing, rootId) - const lipmaaDepth = lipmaa(depth + 1) - 1 - const prev = calculatePrev(existing, depth, lipmaaDepth, rootId) + const tangle = opts.tangles[rootId] + const depth = tangle.getMaxDepth() + 1 + const tips = tangle.getTips() + const lipmaaSet = tangle.getLipmaaSet(depth) + const prev = [...union(lipmaaSet, tips)] tangles[rootId] = { depth, prev } } } else { diff --git a/lib/feed-v1/validation.js b/lib/feed-v1/validation.js index ba02966..f6a9c6d 100644 --- a/lib/feed-v1/validation.js +++ b/lib/feed-v1/validation.js @@ -1,6 +1,7 @@ const base58 = require('bs58') const ed25519 = require('ssb-keys/sodium') const stringify = require('fast-json-stable-stringify') +const Tangle = require('../tangle') function validateShape(msg) { if (!msg || typeof msg !== 'object') { @@ -73,13 +74,23 @@ function validateSignature(msg) { } } -function validateTangle(msg, existingMsgs, tangleId) { - const tangle = msg.metadata.tangles[tangleId] - if (!tangle?.prev || !Array.isArray(tangle.prev)) { +/** + * + * @param {any} msg + * @param {Tangle} tangle + * @param {*} tangleId + * @returns + */ +function validateTangle(msg, tangle, tangleId) { + if (!msg.metadata.tangles[tangleId]) { + return new Error('invalid message: must have metadata.tangles.' + tangleId) + } + const { depth, prev } = msg.metadata.tangles[tangleId] + if (!prev || !Array.isArray(prev)) { // prettier-ignore return new Error('invalid message: prev must be an array, on feed: ' + msg.metadata.who); } - for (const p of tangle.prev) { + for (const p of prev) { if (typeof p !== 'string') { // prettier-ignore return new Error('invalid message: prev must contain strings but found ' + p + ', on feed: ' + msg.metadata.who); @@ -89,18 +100,13 @@ function validateTangle(msg, existingMsgs, tangleId) { return new Error('invalid message: prev must not contain URIs, on feed: ' + msg.metadata.who); } - if (!existingMsgs.has(p)) { + if (!tangle.has(p)) { // prettier-ignore return new Error('invalid message: prev ' + p + ' is not locally known, on feed: ' + msg.metadata.who); } - const existingMsg = existingMsgs.get(p) + const prevDepth = tangle.getDepth(p) - if (existingMsg.metadata.type !== msg.metadata.type) { - // prettier-ignore - return new Error('invalid message: prev ' + p + ' is not from the same type, on feed: ' + msg.metadata.who); - } - const existingDepth = existingMsg.metadata.tangles[tangleId]?.depth ?? 0 - if (existingDepth >= tangle.depth) { + if (prevDepth >= depth) { // prettier-ignore return new Error('invalid message: depth of prev ' + p + ' is not lower, on feed: ' + msg.metadata.who); } @@ -158,7 +164,7 @@ function validateContent(msg) { // FIXME: validateDepth should be +1 of the max of prev depth -function validateSync(msg, existingMsgs, msgHash, rootHash) { +function validateSync(msg, tangle, msgHash, rootHash) { let err if ((err = validateShape(msg))) return err if ((err = validateWho(msg))) return err @@ -166,15 +172,15 @@ function validateSync(msg, existingMsgs, msgHash, rootHash) { if (msgHash === rootHash) { if ((err = validateTangleRoot(msg))) return err } else { - if ((err = validateTangle(msg, existingMsgs, rootHash))) return err + if ((err = validateTangle(msg, tangle, rootHash))) return err } if ((err = validateContent(msg))) return err if ((err = validateSignature(msg))) return err } -function validate(msg, existingMsgs, msgHash, rootHash, cb) { +function validate(msg, tangle, msgHash, rootHash, cb) { let err - if ((err = validateSync(msg, existingMsgs, msgHash, rootHash))) { + if ((err = validateSync(msg, tangle, msgHash, rootHash))) { return cb(err) } cb() diff --git a/lib/plugin.js b/lib/plugin.js index ee9a94a..dd3fc60 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -4,6 +4,7 @@ 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, isEmptyObject } = require('./utils') const { decrypt } = require('./encryption') @@ -149,10 +150,10 @@ 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 tangleMsgs = populateTangle(tangleRootHash) + const tangle = new Tangle(tangleRootHash, records()) const msgHash = FeedV1.getMsgHash(msg) - FeedV1.validate(msg, tangleMsgs, msgHash, tangleRootHash, validationCB) + FeedV1.validate(msg, tangle, msgHash, tangleRootHash, validationCB) function validationCB(err) { // prettier-ignore @@ -180,20 +181,10 @@ exports.init = function initDB(peer, config) { return null } - function populateTangle(tangleId) { - const map = new Map() - for (const rec of records()) { - if (rec.hash === tangleId || rec.msg.metadata.tangles?.[tangleId]) { - map.set(rec.hash, rec.msg) - } - } - return map - } - function populateTangles(tangleIds) { const tangles = {} for (const tangleId of tangleIds) { - tangles[tangleId] ??= populateTangle(tangleId) + tangles[tangleId] ??= new Tangle(tangleId, records()) } return tangles } @@ -325,5 +316,8 @@ exports.init = function initDB(peer, config) { // internal findEncryptionFormatFor, + + // mockable by tests + _getLog: () => log, } } diff --git a/lib/tangle.js b/lib/tangle.js index 88b299d..8e99b15 100644 --- a/lib/tangle.js +++ b/lib/tangle.js @@ -40,6 +40,11 @@ function compareMsgHashes(a, b) { } class Tangle { + /** + * @type {string} + */ + #rootHash + /** * @type {Set} */ @@ -70,30 +75,34 @@ class Tangle { * @param {string} rootHash * @param {Iterable} recordsIter */ - constructor(rootHash, recordsIter) { + constructor(rootHash, recordsIter = []) { + this.#rootHash = rootHash this.#maxDepth = 0 for (const rec of recordsIter) { - const msgHash = rec.hash - const tangles = rec.msg.metadata.tangles - if (msgHash === rootHash) { - this.#tips.add(msgHash) - this.#perDepth.set(0, [msgHash]) - this.#depth.set(msgHash, 0) - } else if (tangles[rootHash]) { - this.#tips.add(msgHash) - const prev = tangles[rootHash].prev - for (const p of prev) { - this.#tips.delete(p) - } - this.#prev.set(msgHash, prev) - const depth = tangles[rootHash].depth - if (depth > this.#maxDepth) this.#maxDepth = depth - this.#depth.set(msgHash, depth) - const atDepth = this.#perDepth.get(depth) ?? [] - atDepth.push(msgHash) - atDepth.sort(compareMsgHashes) - this.#perDepth.set(depth, atDepth) + this.add(rec.hash, rec.msg) + } + } + + add(msgHash, msg) { + const tangles = msg.metadata.tangles + if (msgHash === this.#rootHash) { + this.#tips.add(msgHash) + this.#perDepth.set(0, [msgHash]) + this.#depth.set(msgHash, 0) + } else if (tangles[this.#rootHash]) { + this.#tips.add(msgHash) + const prev = tangles[this.#rootHash].prev + for (const p of prev) { + this.#tips.delete(p) } + this.#prev.set(msgHash, prev) + const depth = tangles[this.#rootHash].depth + if (depth > this.#maxDepth) this.#maxDepth = depth + this.#depth.set(msgHash, depth) + const atDepth = this.#perDepth.get(depth) ?? [] + atDepth.push(msgHash) + atDepth.sort(compareMsgHashes) + this.#perDepth.set(depth, atDepth) } } diff --git a/package.json b/package.json index 8c75815..a1c83b4 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "obz": "^1.1.0", "promisify-4loc": "^1.0.0", "push-stream": "^11.2.0", + "set.prototype.union": "^1.0.2", "ssb-uri2": "^2.4.1" }, "devDependencies": { @@ -44,7 +45,7 @@ "tape": "^5.6.3" }, "scripts": { - "test": "tape test/*.js | tap-arc --bail", + "test": "tape test/*.test.js | tap-arc --bail", "format-code": "prettier --write \"*.js\" \"(test|compat|indexes|operators)/*.js\"", "format-code-staged": "pretty-quick --staged --pattern \"*.js\" --pattern \"(test|compat|indexes|operators)/*.js\"", "coverage": "c8 --reporter=lcov npm run test" diff --git a/test/add.test.js b/test/add.test.js index fe8abf7..3857961 100644 --- a/test/add.test.js +++ b/test/add.test.js @@ -5,6 +5,7 @@ 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') @@ -32,7 +33,7 @@ test('add()', async (t) => { type: 'post', content: { text: 'This is the first post!' }, tangles: { - [rootHash]: new Map([[FeedV1.getMsgHash(rootMsg), rootMsg]]), + [rootHash]: new Tangle(rootHash, [recRoot]), }, }) diff --git a/test/create.test.js b/test/create.test.js index 6a70c11..0043569 100644 --- a/test/create.test.js +++ b/test/create.test.js @@ -6,6 +6,7 @@ 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') @@ -72,9 +73,9 @@ test('add() forked then create() merged', async (t) => { type: 'post', content: { text: '3rd post forked from 1st' }, tangles: { - [rootHash]: new Map([ - [rootHash, rootMsg], - [rec1.hash, rec1.msg], + [rootHash]: new Tangle(rootHash, [ + { hash: rootHash, msg: rootMsg }, + rec1, ]), }, }) diff --git a/test/feed-v1-create.test.js b/test/feed-v1-create.test.js index a666b6d..52be52c 100644 --- a/test/feed-v1-create.test.js +++ b/test/feed-v1-create.test.js @@ -1,5 +1,6 @@ const tape = require('tape') const FeedV1 = require('../lib/feed-v1') +const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') let rootMsg = null @@ -30,7 +31,7 @@ tape('FeedV1.create()', (t) => { content, type: 'post', tangles: { - [rootHash]: new Map([[rootHash, rootMsg]]), + [rootHash]: new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]), }, when, }) @@ -71,9 +72,9 @@ tape('FeedV1.create()', (t) => { content: content2, type: 'post', tangles: { - [rootHash]: new Map([ - [rootHash, rootMsg], - [msgHash1, msg1], + [rootHash]: new Tangle(rootHash, [ + { hash: rootHash, msg: rootMsg }, + { hash: msgHash1, msg: msg1 }, ]), }, when: when + 1, @@ -111,14 +112,14 @@ tape('FeedV1.create()', (t) => { tape('create() handles DAG tips correctly', (t) => { const keys = generateKeypair('alice') const when = 1652037377204 - const existing = new Map([[rootHash, rootMsg]]) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content: { text: '1' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 1, }) @@ -129,14 +130,14 @@ tape('create() handles DAG tips correctly', (t) => { 'msg1.prev is root' ) - existing.set(msgHash1, msg1) + tangle.add(msgHash1, msg1) const msg2A = FeedV1.create({ keys, content: { text: '2A' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 2, }) @@ -151,9 +152,8 @@ tape('create() handles DAG tips correctly', (t) => { content: { text: '2B' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, - existing, when: when + 2, }) const msgHash2B = FeedV1.getMsgHash(msg2B) @@ -163,14 +163,14 @@ tape('create() handles DAG tips correctly', (t) => { 'msg2B.prev is msg1' ) - existing.set(msgHash2B, msg2B) + tangle.add(msgHash2B, msg2B) const msg3 = FeedV1.create({ keys, content: { text: '3' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 3, }) @@ -180,10 +180,10 @@ tape('create() handles DAG tips correctly', (t) => { [rootHash, msgHash2B], 'msg3.prev is root(lipmaa),msg2B(previous)' ) - existing.set(msgHash3, msg3) + tangle.add(msgHash3, msg3) const msgHash2A = FeedV1.getMsgHash(msg2A) - existing.set(msgHash2A, msg2A) + tangle.add(msgHash2A, msg2A) t.pass('msg2A comes into awareness') const msg4 = FeedV1.create({ @@ -191,7 +191,7 @@ tape('create() handles DAG tips correctly', (t) => { content: { text: '4' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 4, }) diff --git a/test/feed-v1-invalid-prev.test.js b/test/feed-v1-invalid-prev.test.js index 7edeaa5..d72860e 100644 --- a/test/feed-v1-invalid-prev.test.js +++ b/test/feed-v1-invalid-prev.test.js @@ -1,6 +1,7 @@ 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) => { @@ -9,21 +10,21 @@ tape('invalid msg with non-array prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const existing = new Map([[rootHash, rootMsg]]) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) msg.metadata.tangles[rootHash].prev = null const msgHash = FeedV1.getMsgHash(msg) - FeedV1.validate(msg, existing, msgHash, rootHash, (err) => { + FeedV1.validate(msg, tangle, msgHash, rootHash, (err) => { t.ok(err, 'invalid 2nd msg throws') t.match(err.message, /prev must be an array/, 'invalid 2nd msg description') t.end() @@ -36,26 +37,26 @@ tape('invalid msg with bad prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const existing = new Map([[rootHash, rootMsg]]) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) const msgHash1 = FeedV1.getMsgHash(msg1) - existing.set(msgHash1, msg1) + tangle.add(msgHash1, msg1) const msg2 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030002000, }) @@ -63,7 +64,7 @@ tape('invalid msg with bad prev', (t) => { msg2.metadata.tangles[rootHash].prev = [1234] const msgHash2 = FeedV1.getMsgHash(msg2) - FeedV1.validate(msg2, existing, msgHash2, rootHash, (err) => { + FeedV1.validate(msg2, tangle, msgHash2, rootHash, (err) => { t.ok(err, 'invalid 2nd msg throws') t.match( err.message, @@ -80,26 +81,26 @@ tape('invalid msg with URI in prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const existing = new Map([[rootHash, rootMsg]]) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) const msgHash1 = FeedV1.getMsgHash(msg1) - existing.set(msgHash1, msg1) + tangle.add(msgHash1, msg1) const msg2 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030002000, }) @@ -109,7 +110,7 @@ tape('invalid msg with URI in prev', (t) => { msg2.metadata.tangles[rootHash].depth = 1 msg2.metadata.tangles[rootHash].prev = [fakeMsgKey1] - FeedV1.validate(msg2, existing, msgHash2, rootHash, (err) => { + FeedV1.validate(msg2, tangle, msgHash2, rootHash, (err) => { t.ok(err, 'invalid 2nd msg throws') t.match( err.message, @@ -126,26 +127,26 @@ tape('invalid msg with unknown prev', (t) => { const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - const existing = new Map([[rootHash, rootMsg]]) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) const msgHash1 = FeedV1.getMsgHash(msg1) - existing.set(msgHash1, msg1) + tangle.add(msgHash1, msg1) const unknownMsg = FeedV1.create({ keys, content: { text: 'Alien' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) @@ -156,13 +157,16 @@ tape('invalid msg with unknown prev', (t) => { content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: new Map([[rootHash, rootMsg], [unknownMsgHash, unknownMsg]]), + [rootHash]: new Tangle(rootHash, [ + { hash: rootHash, msg: rootMsg }, + { hash: unknownMsgHash, msg: unknownMsg }, + ]), }, when: 1652030002000, }) const msgHash2 = FeedV1.getMsgHash(msg2) - FeedV1.validate(msg2, existing, msgHash2, rootHash, (err) => { + FeedV1.validate(msg2, tangle, msgHash2, rootHash, (err) => { t.ok(err, 'invalid 2nd msg throws') t.match( err.message, diff --git a/test/feed-v1-lipmaa.test.js b/test/feed-v1-lipmaa.test.js index 2e7c01c..9057b98 100644 --- a/test/feed-v1-lipmaa.test.js +++ b/test/feed-v1-lipmaa.test.js @@ -1,28 +1,28 @@ const tape = require('tape') const FeedV1 = require('../lib/feed-v1') +const Tangle = require('../lib/tangle') const { generateKeypair } = require('./util') tape('lipmaa prevs', (t) => { const keys = generateKeypair('alice') const content = { text: 'Hello world!' } const when = 1652037377204 - const existing = new Map() const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - existing.set(rootHash, rootMsg) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 1, }) const msgHash1 = FeedV1.getMsgHash(msg1) - existing.set(msgHash1, msg1) + tangle.add(msgHash1, msg1) t.equals(msg1.metadata.tangles[rootHash].depth, 1, 'msg1 depth') t.deepEquals(msg1.metadata.tangles[rootHash].prev, [rootHash], 'msg1 prev') @@ -31,30 +31,26 @@ tape('lipmaa prevs', (t) => { content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 2, }) const msgHash2 = FeedV1.getMsgHash(msg2) - existing.set(msgHash2, msg2) + tangle.add(msgHash2, msg2) t.equals(msg2.metadata.tangles[rootHash].depth, 2, 'msg2 depth') - t.deepEquals( - msg2.metadata.tangles[rootHash].prev, - [msgHash1], - 'msg2 prev' - ) + t.deepEquals(msg2.metadata.tangles[rootHash].prev, [msgHash1], 'msg2 prev') const msg3 = FeedV1.create({ keys, content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 3, }) const msgHash3 = FeedV1.getMsgHash(msg3) - existing.set(msgHash3, msg3) + tangle.add(msgHash3, msg3) t.equals(msg3.metadata.tangles[rootHash].depth, 3, 'msg3 depth') t.deepEquals( msg3.metadata.tangles[rootHash].prev, @@ -67,66 +63,54 @@ tape('lipmaa prevs', (t) => { content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 4, }) const msgHash4 = FeedV1.getMsgHash(msg4) - existing.set(msgHash4, msg4) + tangle.add(msgHash4, msg4) t.equals(msg4.metadata.tangles[rootHash].depth, 4, 'msg4 depth') - t.deepEquals( - msg4.metadata.tangles[rootHash].prev, - [msgHash3], - 'msg4 prev' - ) + t.deepEquals(msg4.metadata.tangles[rootHash].prev, [msgHash3], 'msg4 prev') const msg5 = FeedV1.create({ keys, content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 5, }) const msgHash5 = FeedV1.getMsgHash(msg5) - existing.set(msgHash5, msg5) + tangle.add(msgHash5, msg5) t.equals(msg5.metadata.tangles[rootHash].depth, 5, 'msg5 depth') - t.deepEquals( - msg5.metadata.tangles[rootHash].prev, - [msgHash4], - 'msg5 prev' - ) + t.deepEquals(msg5.metadata.tangles[rootHash].prev, [msgHash4], 'msg5 prev') const msg6 = FeedV1.create({ keys, content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 6, }) const msgHash6 = FeedV1.getMsgHash(msg6) - existing.set(msgHash6, msg6) + tangle.add(msgHash6, msg6) t.equals(msg6.metadata.tangles[rootHash].depth, 6, 'msg6 depth') - t.deepEquals( - msg6.metadata.tangles[rootHash].prev, - [msgHash5], - 'msg6 prev' - ) + t.deepEquals(msg6.metadata.tangles[rootHash].prev, [msgHash5], 'msg6 prev') const msg7 = FeedV1.create({ keys, content, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: when + 7, }) const msgHash7 = FeedV1.getMsgHash(msg7) - existing.set(msgHash7, msg7) + tangle.add(msgHash7, msg7) t.equals(msg7.metadata.tangles[rootHash].depth, 7, 'msg7 depth') t.deepEquals( msg7.metadata.tangles[rootHash].prev, @@ -134,6 +118,5 @@ tape('lipmaa prevs', (t) => { 'msg7 prev (has lipmaa!)' ) - t.end() }) diff --git a/test/feed-v1-tangles.test.js b/test/feed-v1-tangles.test.js index 66b6d5a..8d832eb 100644 --- a/test/feed-v1-tangles.test.js +++ b/test/feed-v1-tangles.test.js @@ -1,27 +1,26 @@ 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) => { const keysA = generateKeypair('alice') const keysB = generateKeypair('bob') - const existingA = new Map() - const existingB = new Map() const rootMsgA = FeedV1.createRoot(keysA, 'post') const rootHashA = FeedV1.getMsgHash(rootMsgA) - existingA.set(rootHashA, rootMsgA) + const tangleA = new Tangle(rootHashA, [{ hash: rootHashA, msg: rootMsgA }]) const rootMsgB = FeedV1.createRoot(keysB, 'post') const rootHashB = FeedV1.getMsgHash(rootMsgB) - existingB.set(rootHashB, rootMsgB) + const tangleB = new Tangle(rootHashB, [{ hash: rootHashB, msg: rootMsgB }]) const msg1 = FeedV1.create({ keys: keysA, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHashA]: existingA, + [rootHashA]: tangleA, }, when: 1652030001000, }) @@ -36,10 +35,9 @@ tape('simple multi-author tangle', (t) => { keys: keysB, content: { text: 'Hello world!' }, type: 'post', - existing: new Map(), tangles: { - [rootHashB]: existingB, - [msgHash1]: new Map([[msgHash1, msg1]]), + [rootHashB]: tangleB, + [msgHash1]: new Tangle(msgHash1, [{ hash: msgHash1, msg: msg1 }]), }, when: 1652030002000, }) @@ -72,48 +70,47 @@ tape('lipmaa in multi-author tangle', (t) => { const content = { text: 'Hello world!' } const when = 1652037377204 - const existingA = new Map() - const existingB = new Map() - const tangleExisting = new Map() const rootMsgA = FeedV1.createRoot(keysA, 'post') const rootHashA = FeedV1.getMsgHash(rootMsgA) - existingA.set(rootHashA, rootMsgA) + const tangleA = new Tangle(rootHashA, [{ hash: rootHashA, msg: rootMsgA }]) const rootMsgB = FeedV1.createRoot(keysB, 'post') const rootHashB = FeedV1.getMsgHash(rootMsgB) - existingB.set(rootHashB, rootMsgB) - + const tangleB = new Tangle(rootHashB, [{ hash: rootHashB, msg: rootMsgB }]) const msg1 = FeedV1.create({ keys: keysA, content, type: 'post', tangles: { - [rootHashA]: existingA, + [rootHashA]: tangleA, }, when: when + 1, }) const msgHash1 = FeedV1.getMsgHash(msg1) - existingA.set(msgHash1, msg1) - tangleExisting.set(msgHash1, msg1) + tangleA.add(msgHash1, msg1) + const tangleThread = new Tangle(msgHash1, [{ hash: msgHash1, msg: msg1 }]) - t.deepEquals(Object.keys(msg1.metadata.tangles),[rootHashA], 'A:msg1 has only feed tangle') + t.deepEquals( + Object.keys(msg1.metadata.tangles), + [rootHashA], + 'A:msg1 has only feed tangle' + ) const msg2 = FeedV1.create({ keys: keysB, content, type: 'post', - existing: existingB, tangles: { - [rootHashB]: existingB, - [msgHash1]: tangleExisting, + [rootHashB]: tangleB, + [msgHash1]: tangleThread, }, when: when + 2, }) const msgHash2 = FeedV1.getMsgHash(msg2) - existingB.set(msgHash2, msg2) - tangleExisting.set(msgHash2, msg2) + tangleB.add(msgHash2, msg2) + tangleThread.add(msgHash2, msg2) t.deepEquals( msg2.metadata.tangles[msgHash1].prev, @@ -125,16 +122,15 @@ tape('lipmaa in multi-author tangle', (t) => { keys: keysB, content, type: 'post', - existing: existingB, tangles: { - [rootHashB]: existingB, - [msgHash1]: tangleExisting, + [rootHashB]: tangleB, + [msgHash1]: tangleThread, }, when: when + 3, }) const msgHash3 = FeedV1.getMsgHash(msg3) - existingB.set(msgHash3, msg3) - tangleExisting.set(msgHash3, msg3) + tangleB.add(msgHash3, msg3) + tangleThread.add(msgHash3, msg3) t.deepEquals( msg3.metadata.tangles[msgHash1].prev, @@ -146,16 +142,15 @@ tape('lipmaa in multi-author tangle', (t) => { keys: keysA, content, type: 'post', - existing: existingA, tangles: { - [rootHashA]: existingA, - [msgHash1]: tangleExisting, + [rootHashA]: tangleA, + [msgHash1]: tangleThread, }, when: when + 4, }) const msgHash4 = FeedV1.getMsgHash(msg4) - existingB.set(msgHash4, msg4) - tangleExisting.set(msgHash4, msg4) + tangleB.add(msgHash4, msg4) + tangleThread.add(msgHash4, msg4) t.deepEquals( msg4.metadata.tangles[msgHash1].prev, diff --git a/test/feed-v1-validate.test.js b/test/feed-v1-validate.test.js index 07d4358..0916f2e 100644 --- a/test/feed-v1-validate.test.js +++ b/test/feed-v1-validate.test.js @@ -1,17 +1,17 @@ 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) => { const keys = generateKeypair('alice') - const existing = new Map() const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - existing.set(rootHash, rootMsg) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) - FeedV1.validate(rootMsg, existing, rootHash, rootHash, (err) => { + FeedV1.validate(rootMsg, tangle, rootHash, rootHash, (err) => { if (err) console.log(err) t.error(err, 'valid root msg') t.end() @@ -20,25 +20,24 @@ tape('validate root msg', (t) => { tape('validate 2nd msg with existing root', (t) => { const keys = generateKeypair('alice') - const existing = new Map() const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - existing.set(rootHash, rootMsg) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) const msgHash1 = FeedV1.getMsgHash(msg1) - existing.set(msgHash1, msg1) + tangle.add(msgHash1, msg1) - FeedV1.validate(msg1, existing, msgHash1, rootHash, (err) => { + FeedV1.validate(msg1, tangle, msgHash1, rootHash, (err) => { if (err) console.log(err) t.error(err, 'valid 2nd msg') t.end() @@ -48,18 +47,16 @@ tape('validate 2nd msg with existing root', (t) => { tape('validate 2nd forked msg', (t) => { const keys = generateKeypair('alice') - const existing = new Map() - const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - existing.set(rootHash, rootMsg) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1A = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, existing: new Map(), when: 1652030001000, @@ -71,15 +68,15 @@ tape('validate 2nd forked msg', (t) => { content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030002000, }) const msgHash1B = FeedV1.getMsgHash(msg1B) - existing.set(msgHash1A, msg1A) - existing.set(msgHash1B, msg1B) - FeedV1.validate(msg1B, existing, msgHash1B, rootHash, (err) => { + tangle.add(msgHash1A, msg1A) + tangle.add(msgHash1B, msg1B) + FeedV1.validate(msg1B, tangle, msgHash1B, rootHash, (err) => { if (err) console.log(err) t.error(err, 'valid 2nd forked msg') t.end() @@ -89,18 +86,16 @@ tape('validate 2nd forked msg', (t) => { tape('invalid msg with unknown previous', (t) => { const keys = generateKeypair('alice') - const existing = new Map() - const rootMsg = FeedV1.createRoot(keys, 'post') const rootHash = FeedV1.getMsgHash(rootMsg) - existing.set(rootHash, rootMsg) + const tangle = new Tangle(rootHash, [{ hash: rootHash, msg: rootMsg }]) const msg1 = FeedV1.create({ keys, content: { text: 'Hello world!' }, type: 'post', tangles: { - [rootHash]: existing, + [rootHash]: tangle, }, when: 1652030001000, }) @@ -110,7 +105,7 @@ tape('invalid msg with unknown previous', (t) => { msg1.metadata.tangles[rootHash].prev = [fakeMsgHash] - FeedV1.validate(msg1, existing, msgHash1, rootHash, (err) => { + FeedV1.validate(msg1, tangle, msgHash1, rootHash, (err) => { t.ok(err, 'invalid 2nd msg throws') t.match( err.message,