mirror of https://codeberg.org/pzp/pzp-db.git
multi-tangle msgs
This commit is contained in:
parent
94ba7246e5
commit
544ca09e9c
|
@ -15,8 +15,19 @@ const {
|
||||||
validateOOO,
|
validateOOO,
|
||||||
validateBatch,
|
validateBatch,
|
||||||
validateOOOBatch,
|
validateOOOBatch,
|
||||||
|
validateMsgHash,
|
||||||
} = require('./validation')
|
} = require('./validation')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Iterator<Msg> & {values: () => Iterator<Msg>}} MsgIter
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} TangleData
|
||||||
|
* @property {number} depth
|
||||||
|
* @property {Array<string>} prev
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} Msg
|
* @typedef {Object} Msg
|
||||||
* @property {*} content
|
* @property {*} content
|
||||||
|
@ -25,6 +36,7 @@ const {
|
||||||
* @property {Array<string>} metadata.prev
|
* @property {Array<string>} metadata.prev
|
||||||
* @property {string} metadata.proof
|
* @property {string} metadata.proof
|
||||||
* @property {number} metadata.size
|
* @property {number} metadata.size
|
||||||
|
* @property {Record<string, TangleData>=} metadata.tangles
|
||||||
* @property {string=} metadata.type
|
* @property {string=} metadata.type
|
||||||
* @property {string} metadata.who
|
* @property {string} metadata.who
|
||||||
* @property {number=} metadata.when
|
* @property {number=} metadata.when
|
||||||
|
@ -39,7 +51,8 @@ const {
|
||||||
* @property {Object} keys
|
* @property {Object} keys
|
||||||
* @property {string} keys.id
|
* @property {string} keys.id
|
||||||
* @property {string} keys.private
|
* @property {string} keys.private
|
||||||
* @property {Iterator<Msg> & {values: () => Iterator<Msg>}} existing
|
* @property {MsgIter} existing
|
||||||
|
* @property {Record<string, MsgIter>=} tangles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,19 +135,45 @@ function calculatePrev(existing, depth, lipmaaDepth) {
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevalidatePrevious(prev, name) {
|
function prevalidateExisting(existing, tangleId = null) {
|
||||||
if (!prev?.[Symbol.iterator]) {
|
if (!existing?.[Symbol.iterator]) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
throw new Error(`opts.${name} must be an iterator, but got ${typeof prev}`)
|
return new Error(`existing must be an iterator, but got ${typeof existing}`)
|
||||||
}
|
}
|
||||||
if (typeof prev?.values !== 'function') {
|
if (typeof existing?.values !== 'function') {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
throw new Error(`opts.${name} must be a Map, Set, or Array, but got ${prev}`)
|
return new Error(`existing must be a Map, Set, or Array, but got ${existing}`)
|
||||||
}
|
}
|
||||||
for (const p of prev.values()) {
|
let isEmpty = true
|
||||||
|
let hasDepthZeroMsg = false
|
||||||
|
for (const p of existing.values()) {
|
||||||
|
isEmpty = false
|
||||||
if (!p.metadata) {
|
if (!p.metadata) {
|
||||||
throw new Error(`opts.${name} must contain messages, but got ${typeof p}`)
|
// prettier-ignore
|
||||||
|
return new Error(`existing must contain messages, but got ${typeof p}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!tangleId && p.metadata.depth === 0) {
|
||||||
|
if (hasDepthZeroMsg) {
|
||||||
|
// prettier-ignore
|
||||||
|
return new Error(`existing must contain only 1 message with depth 0`)
|
||||||
|
} else {
|
||||||
|
hasDepthZeroMsg = true
|
||||||
|
}
|
||||||
|
} else if (tangleId) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isEmpty && !hasDepthZeroMsg) {
|
||||||
|
// prettier-ignore
|
||||||
|
return new Error(`opts.existing must contain the message with depth 0`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,12 +184,27 @@ function prevalidatePrevious(prev, name) {
|
||||||
function create(opts) {
|
function create(opts) {
|
||||||
let err
|
let err
|
||||||
if ((err = validateType(opts.type))) throw err
|
if ((err = validateType(opts.type))) throw err
|
||||||
prevalidatePrevious(opts.existing, 'existing')
|
if ((err = prevalidateExisting(opts.existing))) throw err
|
||||||
|
|
||||||
const [proof, size] = representContent(opts.content)
|
const [proof, size] = representContent(opts.content)
|
||||||
const depth = calculateDepth(opts.existing)
|
const depth = calculateDepth(opts.existing)
|
||||||
const lipmaaDepth = lipmaa(depth + 1) - 1
|
const lipmaaDepth = lipmaa(depth + 1) - 1
|
||||||
const prev = calculatePrev(opts.existing, depth, lipmaaDepth)
|
const prev = calculatePrev(opts.existing, depth, lipmaaDepth)
|
||||||
|
|
||||||
|
let tangles = null
|
||||||
|
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)
|
||||||
|
const lipmaaDepth = lipmaa(depth + 1) - 1
|
||||||
|
const prev = calculatePrev(existing, depth, lipmaaDepth)
|
||||||
|
tangles ??= {}
|
||||||
|
tangles[rootId] = { depth, prev }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
content: opts.content,
|
content: opts.content,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
@ -158,6 +212,7 @@ function create(opts) {
|
||||||
prev,
|
prev,
|
||||||
proof,
|
proof,
|
||||||
size,
|
size,
|
||||||
|
...(tangles ? { tangles } : null),
|
||||||
type: opts.type,
|
type: opts.type,
|
||||||
who: stripAuthor(opts.keys.id),
|
who: stripAuthor(opts.keys.id),
|
||||||
when: +opts.when,
|
when: +opts.when,
|
||||||
|
|
|
@ -41,6 +41,14 @@ function validateWho(msg) {
|
||||||
// FIXME: if there are prev, then `who` must match
|
// FIXME: if there are prev, then `who` must match
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateMsgHash(str) {
|
||||||
|
try {
|
||||||
|
base58.decode(str)
|
||||||
|
} catch (err) {
|
||||||
|
return new Error(`invalid message: msgHash ${str} should have been a base58 string`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateSignature(msg) {
|
function validateSignature(msg) {
|
||||||
const { sig } = msg
|
const { sig } = msg
|
||||||
if (typeof sig !== 'string') {
|
if (typeof sig !== 'string') {
|
||||||
|
@ -224,6 +232,7 @@ module.exports = {
|
||||||
validateContent,
|
validateContent,
|
||||||
|
|
||||||
validate,
|
validate,
|
||||||
|
validateMsgHash,
|
||||||
// validateBatch,
|
// validateBatch,
|
||||||
// validateOOO,
|
// validateOOO,
|
||||||
// validateOOOBatch,
|
// validateOOOBatch,
|
||||||
|
|
|
@ -3,28 +3,6 @@ const base58 = require('bs58')
|
||||||
const FeedV1 = require('../lib/feed-v1')
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
const { generateKeypair } = require('./util')
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
tape('invalid 1st msg with non-empty prev', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
|
|
||||||
const msg = FeedV1.create({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
existing: new Map([['1234', { metadata: { depth: 10 }, sig: 'fake' }]]),
|
|
||||||
when: 1652030001000,
|
|
||||||
})
|
|
||||||
|
|
||||||
FeedV1.validate(msg, new Map(), (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/prev .+ is not locally known/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid 1st msg with non-array prev', (t) => {
|
tape('invalid 1st msg with non-array prev', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
@ -60,7 +38,7 @@ tape('invalid msg with non-array prev', (t) => {
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
existing: new Map([['1234', { metadata: { depth: 10 }, sig: 'fake' }]]),
|
existing: new Map(),
|
||||||
when: 1652030002000,
|
when: 1652030002000,
|
||||||
})
|
})
|
||||||
msg2.metadata.prev = null
|
msg2.metadata.prev = null
|
||||||
|
@ -71,7 +49,7 @@ tape('invalid msg with non-array prev', (t) => {
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
t.match(
|
t.match(
|
||||||
err.message,
|
err.message,
|
||||||
/prev must be an iterator/,
|
/prev must be an array/,
|
||||||
'invalid 2nd msg description'
|
'invalid 2nd msg description'
|
||||||
)
|
)
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -94,9 +72,10 @@ tape('invalid msg with bad prev', (t) => {
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
existing: new Map([['1234', { metadata: { depth: 10 }, sig: 'fake' }]]),
|
existing: new Map(),
|
||||||
when: 1652030002000,
|
when: 1652030002000,
|
||||||
})
|
})
|
||||||
|
msg2.metadata.depth = 1
|
||||||
msg2.metadata.prev = [1234]
|
msg2.metadata.prev = [1234]
|
||||||
|
|
||||||
const existing = new Map()
|
const existing = new Map()
|
||||||
|
@ -128,11 +107,12 @@ tape('invalid msg with URI in prev', (t) => {
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
existing: new Map([['1234', { metadata: { depth: 10 }, sig: 'fake' }]]),
|
existing: new Map(),
|
||||||
when: 1652030002000,
|
when: 1652030002000,
|
||||||
})
|
})
|
||||||
const randBuf = Buffer.alloc(16).fill(16)
|
const randBuf = Buffer.alloc(16).fill(16)
|
||||||
const fakeMsgKey1 = `ppppp:message/v1/${base58.encode(randBuf)}`
|
const fakeMsgKey1 = `ppppp:message/v1/${base58.encode(randBuf)}`
|
||||||
|
msg2.metadata.depth = 1
|
||||||
msg2.metadata.prev = [fakeMsgKey1]
|
msg2.metadata.prev = [fakeMsgKey1]
|
||||||
|
|
||||||
const existing = new Map()
|
const existing = new Map()
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
const tape = require('tape')
|
||||||
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
|
tape('simple multi-author tangle', (t) => {
|
||||||
|
const keysA = generateKeypair('alice')
|
||||||
|
const keysB = generateKeypair('bob')
|
||||||
|
|
||||||
|
const msg1 = FeedV1.create({
|
||||||
|
keys: keysA,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
existing: new Map(),
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
t.notOk(msg1.metadata.tangles, 'msg1 has no extra tangles')
|
||||||
|
|
||||||
|
const msg2 = FeedV1.create({
|
||||||
|
keys: keysB,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
existing: new Map(),
|
||||||
|
tangles: {
|
||||||
|
[msgHash1]: new Map([[msgHash1, msg1]]),
|
||||||
|
},
|
||||||
|
when: 1652030002000,
|
||||||
|
})
|
||||||
|
t.ok(msg2.metadata.tangles, 'msg2 has extra tangles')
|
||||||
|
t.ok(msg2.metadata.tangles[msgHash1], 'msg2 has tangle for msgHash1')
|
||||||
|
t.equal(msg2.metadata.tangles[msgHash1].depth, 1, 'msg2 has tangle depth 1')
|
||||||
|
t.deepEquals(
|
||||||
|
msg2.metadata.tangles[msgHash1].prev,
|
||||||
|
[msgHash1],
|
||||||
|
'msg2 has tangle prev'
|
||||||
|
)
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
Loading…
Reference in New Issue