alphabetize tangles (#5)

Co-authored-by: André Staltz <andre@staltz.com>
This commit is contained in:
Mikey 2023-05-09 19:31:44 +12:00 committed by GitHub
parent f8bc73d52b
commit e66483391b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 10 deletions

View File

@ -118,7 +118,7 @@ function create(opts) {
const depth = tangle.getMaxDepth() + 1 const depth = tangle.getMaxDepth() + 1
const tips = tangle.getTips() const tips = tangle.getTips()
const lipmaaSet = tangle.getLipmaaSet(depth) const lipmaaSet = tangle.getLipmaaSet(depth)
const prev = [...union(lipmaaSet, tips)] const prev = ([...union(lipmaaSet, tips)]).sort()
tangles[rootId] = { depth, prev } tangles[rootId] = { depth, prev }
} }
} else { } else {

View File

@ -127,6 +127,7 @@ function validateTangle(msg, tangle, tangleId) {
return new Error(`invalid message: who "${msg.metadata.who}" does not match feed who "${who}"`) return new Error(`invalid message: who "${msg.metadata.who}" does not match feed who "${who}"`)
} }
} }
let lastPrev = null
let minDiff = Infinity let minDiff = Infinity
let countPrevUnknown = 0 let countPrevUnknown = 0
for (const p of prev) { for (const p of prev) {
@ -138,6 +139,15 @@ function validateTangle(msg, tangle, tangleId) {
// prettier-ignore // prettier-ignore
return new Error('invalid message: prev must not contain URIs, on feed: ' + msg.metadata.who); 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)) { if (!tangle.has(p)) {
countPrevUnknown += 1 countPrevUnknown += 1

View File

@ -12,7 +12,7 @@ interface Msg {
// for each tangle this msg belongs to, identified by the tangle's root // for each tangle this msg belongs to, identified by the tangle's root
[rootMsgHash: string]: { [rootMsgHash: string]: {
depth: number, // maximum distance (positive integer) from this msg to the root depth: number, // maximum distance (positive integer) from this msg to the root
prev: Array<MsgHash>, // list of msg hashes of existing msgs prev: Array<MsgHash>, // list of msg hashes of existing msgs, unique set and ordered alphabetically
}, },
}, },
type: string, // alphanumeric string, at least 3 chars, max 100 chars type: string, // alphanumeric string, at least 3 chars, max 100 chars

View File

@ -171,8 +171,8 @@ tape('create() handles DAG tips correctly', (t) => {
const msgHash3 = FeedV1.getMsgHash(msg3) const msgHash3 = FeedV1.getMsgHash(msg3)
t.deepEquals( t.deepEquals(
msg3.metadata.tangles[rootHash].prev, msg3.metadata.tangles[rootHash].prev,
[rootHash, msgHash2B], [rootHash, msgHash2B].sort(),
'msg3.prev is root(lipmaa),msg2B(previous)' 'msg3.prev is [root(lipmaa),msg2B(previous)], sorted'
) )
tangle.add(msgHash3, msg3) tangle.add(msgHash3, msg3)
@ -190,8 +190,8 @@ tape('create() handles DAG tips correctly', (t) => {
}) })
t.deepEquals( t.deepEquals(
msg4.metadata.tangles[rootHash].prev, msg4.metadata.tangles[rootHash].prev,
[msgHash3, msgHash2A], [msgHash3, msgHash2A].sort(),
'msg4.prev is [msg3(previous),msg2A(old fork as tip)]' 'msg4.prev is [msg3(previous),msg2A(old fork as tip)], sorted'
) )
t.end() t.end()

View File

@ -220,3 +220,95 @@ tape('invalid feed msg with a different type', (t) => {
) )
t.end() 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()
})

View File

@ -50,7 +50,7 @@ tape('lipmaa prevs', (t) => {
t.equals(msg3.metadata.tangles[rootHash].depth, 3, 'msg3 depth') t.equals(msg3.metadata.tangles[rootHash].depth, 3, 'msg3 depth')
t.deepEquals( t.deepEquals(
msg3.metadata.tangles[rootHash].prev, msg3.metadata.tangles[rootHash].prev,
[rootHash, msgHash2], [rootHash, msgHash2].sort(),
'msg3 prev (has lipmaa!)' 'msg3 prev (has lipmaa!)'
) )
@ -106,7 +106,7 @@ tape('lipmaa prevs', (t) => {
t.equals(msg7.metadata.tangles[rootHash].depth, 7, 'msg7 depth') t.equals(msg7.metadata.tangles[rootHash].depth, 7, 'msg7 depth')
t.deepEquals( t.deepEquals(
msg7.metadata.tangles[rootHash].prev, msg7.metadata.tangles[rootHash].prev,
[msgHash3, msgHash6], [msgHash3, msgHash6].sort(),
'msg7 prev (has lipmaa!)' 'msg7 prev (has lipmaa!)'
) )

View File

@ -46,7 +46,7 @@ tape('simple multi-author tangle', (t) => {
t.deepEquals( t.deepEquals(
Object.keys(msg2.metadata.tangles), Object.keys(msg2.metadata.tangles),
[rootHashB, msgHash1], [rootHashB, msgHash1].sort(),
'msg2 has feed tangle and misc tangle' 'msg2 has feed tangle and misc tangle'
) )
t.equal(msg2.metadata.tangles[rootHashB].depth, 1, 'msg2 feed tangle depth') 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( t.deepEquals(
msg4.metadata.tangles[msgHash1].prev, msg4.metadata.tangles[msgHash1].prev,
[msgHash1, msgHash3], [msgHash1, msgHash3].sort(),
'A:msg4 points to A:msg1,B:msg3' 'A:msg4 points to A:msg1,B:msg3'
) )