From e66483391b17c04a6cea27a5667efbc1b0e8ce60 Mon Sep 17 00:00:00 2001 From: Mikey Date: Tue, 9 May 2023 19:31:44 +1200 Subject: [PATCH] alphabetize tangles (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Staltz --- lib/feed-v1/index.js | 2 +- lib/feed-v1/validation.js | 10 +++ protospec.md | 2 +- test/feed-v1/feed-v1-create.test.js | 8 +- test/feed-v1/feed-v1-invalid-prev.test.js | 92 +++++++++++++++++++++++ test/feed-v1/feed-v1-lipmaa.test.js | 4 +- test/feed-v1/feed-v1-tangles.test.js | 4 +- 7 files changed, 112 insertions(+), 10 deletions(-) diff --git a/lib/feed-v1/index.js b/lib/feed-v1/index.js index 800f24a..57efce7 100644 --- a/lib/feed-v1/index.js +++ b/lib/feed-v1/index.js @@ -118,7 +118,7 @@ function create(opts) { const depth = tangle.getMaxDepth() + 1 const tips = tangle.getTips() const lipmaaSet = tangle.getLipmaaSet(depth) - const prev = [...union(lipmaaSet, tips)] + const prev = ([...union(lipmaaSet, tips)]).sort() tangles[rootId] = { depth, prev } } } else { diff --git a/lib/feed-v1/validation.js b/lib/feed-v1/validation.js index 0a9b626..364fe1f 100644 --- a/lib/feed-v1/validation.js +++ b/lib/feed-v1/validation.js @@ -127,6 +127,7 @@ function validateTangle(msg, tangle, tangleId) { return new Error(`invalid message: who "${msg.metadata.who}" does not match feed who "${who}"`) } } + let lastPrev = null let minDiff = Infinity let countPrevUnknown = 0 for (const p of prev) { @@ -138,6 +139,15 @@ function validateTangle(msg, tangle, tangleId) { // prettier-ignore return new Error('invalid message: prev must not contain URIs, on feed: ' + msg.metadata.who); } + if (lastPrev !== null) { + if (p === lastPrev) { + return new Error(`invalid message: prev must be unique set, on feed ${msg.metadata.who}`) + } + if (p < lastPrev) { + return new Error(`invalid message: prev must be sorted in alphabetical order, on feed ${msg.metadata.who}`) + } + } + lastPrev = p if (!tangle.has(p)) { countPrevUnknown += 1 diff --git a/protospec.md b/protospec.md index 835024f..7da0d53 100644 --- a/protospec.md +++ b/protospec.md @@ -12,7 +12,7 @@ interface Msg { // for each tangle this msg belongs to, identified by the tangle's root [rootMsgHash: string]: { depth: number, // maximum distance (positive integer) from this msg to the root - prev: Array, // list of msg hashes of existing msgs + prev: Array, // list of msg hashes of existing msgs, unique set and ordered alphabetically }, }, type: string, // alphanumeric string, at least 3 chars, max 100 chars diff --git a/test/feed-v1/feed-v1-create.test.js b/test/feed-v1/feed-v1-create.test.js index 199cc59..298fb24 100644 --- a/test/feed-v1/feed-v1-create.test.js +++ b/test/feed-v1/feed-v1-create.test.js @@ -171,8 +171,8 @@ tape('create() handles DAG tips correctly', (t) => { const msgHash3 = FeedV1.getMsgHash(msg3) t.deepEquals( msg3.metadata.tangles[rootHash].prev, - [rootHash, msgHash2B], - 'msg3.prev is root(lipmaa),msg2B(previous)' + [rootHash, msgHash2B].sort(), + 'msg3.prev is [root(lipmaa),msg2B(previous)], sorted' ) tangle.add(msgHash3, msg3) @@ -190,8 +190,8 @@ tape('create() handles DAG tips correctly', (t) => { }) t.deepEquals( msg4.metadata.tangles[rootHash].prev, - [msgHash3, msgHash2A], - 'msg4.prev is [msg3(previous),msg2A(old fork as tip)]' + [msgHash3, msgHash2A].sort(), + 'msg4.prev is [msg3(previous),msg2A(old fork as tip)], sorted' ) t.end() diff --git a/test/feed-v1/feed-v1-invalid-prev.test.js b/test/feed-v1/feed-v1-invalid-prev.test.js index 7ad57cb..acfd033 100644 --- a/test/feed-v1/feed-v1-invalid-prev.test.js +++ b/test/feed-v1/feed-v1-invalid-prev.test.js @@ -220,3 +220,95 @@ tape('invalid feed msg with a different type', (t) => { ) t.end() }) + +tape('invalid feed msg with non-alphabetical prev', (t) => { + const keys = generateKeypair('alice') + + const rootMsg = FeedV1.createRoot(keys, 'post') + const rootHash = FeedV1.getMsgHash(rootMsg) + + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) + + const msg1 = FeedV1.create({ + keys, + content: { text: '1' }, + type: 'post', + tangles: { + [rootHash]: tangle, + }, + }) + const msgHash1 = FeedV1.getMsgHash(msg1) + + const msg2 = FeedV1.create({ + keys, + content: { text: '2' }, + type: 'post', + tangles: { + [rootHash]: tangle, + }, + }) + const msgHash2 = FeedV1.getMsgHash(msg2) + + tangle.add(msgHash1, msg1) + tangle.add(msgHash2, msg2) + + const msg3 = FeedV1.create({ + keys, + content: { text: '3' }, + type: 'post', + tangles: { + [rootHash]: tangle, + }, + }) + const msgHash3 = FeedV1.getMsgHash(msg3) + + let prevHashes = msg3.metadata.tangles[rootHash].prev + if (prevHashes[0] < prevHashes[1]) { + prevHashes = [prevHashes[1], prevHashes[0]] + } else { + prevHashes = [prevHashes[0], prevHashes[1]] + } + msg3.metadata.tangles[rootHash].prev = prevHashes + + const err = FeedV1.validate(msg3, tangle, msgHash3, rootHash) + t.ok(err, 'invalid 3rd msg throws') + t.match( + err.message, + /prev must be sorted in alphabetical order/, + 'invalid error message' + ) + t.end() +}) + +tape('invalid feed msg with duplicate prev', (t) => { + const keys = generateKeypair('alice') + + const rootMsg = FeedV1.createRoot(keys, 'post') + const rootHash = FeedV1.getMsgHash(rootMsg) + + const tangle = new FeedV1.Tangle(rootHash) + tangle.add(rootHash, rootMsg) + + const msg1 = FeedV1.create({ + keys, + content: { text: '1' }, + type: 'post', + tangles: { + [rootHash]: tangle, + }, + }) + const msgHash1 = FeedV1.getMsgHash(msg1) + + const [prevHash] = msg1.metadata.tangles[rootHash].prev + msg1.metadata.tangles[rootHash].prev = [prevHash, prevHash] + + const err = FeedV1.validate(msg1, tangle, msgHash1, rootHash) + t.ok(err, 'invalid 1st msg throws') + t.match( + err.message, + /prev must be unique set/, + 'invalid error message' + ) + t.end() +}) diff --git a/test/feed-v1/feed-v1-lipmaa.test.js b/test/feed-v1/feed-v1-lipmaa.test.js index 02a2d45..52ced42 100644 --- a/test/feed-v1/feed-v1-lipmaa.test.js +++ b/test/feed-v1/feed-v1-lipmaa.test.js @@ -50,7 +50,7 @@ tape('lipmaa prevs', (t) => { t.equals(msg3.metadata.tangles[rootHash].depth, 3, 'msg3 depth') t.deepEquals( msg3.metadata.tangles[rootHash].prev, - [rootHash, msgHash2], + [rootHash, msgHash2].sort(), 'msg3 prev (has lipmaa!)' ) @@ -106,7 +106,7 @@ tape('lipmaa prevs', (t) => { t.equals(msg7.metadata.tangles[rootHash].depth, 7, 'msg7 depth') t.deepEquals( msg7.metadata.tangles[rootHash].prev, - [msgHash3, msgHash6], + [msgHash3, msgHash6].sort(), 'msg7 prev (has lipmaa!)' ) diff --git a/test/feed-v1/feed-v1-tangles.test.js b/test/feed-v1/feed-v1-tangles.test.js index 15ad6a2..75486ca 100644 --- a/test/feed-v1/feed-v1-tangles.test.js +++ b/test/feed-v1/feed-v1-tangles.test.js @@ -46,7 +46,7 @@ tape('simple multi-author tangle', (t) => { t.deepEquals( Object.keys(msg2.metadata.tangles), - [rootHashB, msgHash1], + [rootHashB, msgHash1].sort(), 'msg2 has feed tangle and misc tangle' ) t.equal(msg2.metadata.tangles[rootHashB].depth, 1, 'msg2 feed tangle depth') @@ -154,7 +154,7 @@ tape('lipmaa in multi-author tangle', (t) => { t.deepEquals( msg4.metadata.tangles[msgHash1].prev, - [msgHash1, msgHash3], + [msgHash1, msgHash3].sort(), 'A:msg4 points to A:msg1,B:msg3' )