Compare commits

...

7 Commits

Author SHA1 Message Date
Jacob Karlsson f79914d838 1.0.4 2024-06-05 17:26:03 +02:00
Jacob Karlsson 37022b8969 Export DBTangle type 2024-06-05 17:25:51 +02:00
Jacob Karlsson 995c70fe68 1.0.3 2024-06-01 18:22:47 +02:00
Jacob Karlsson 047c88fb86 Return null instead of throwing on property errs (#8)
Trying to fix https://codeberg.org/pzp/pzp-sync/issues/10

This doesn't solve the problem of missing rootMsg but maybe allows sync to fail more gracefully

Reviewed-on: https://codeberg.org/pzp/pzp-db/pulls/8
Co-authored-by: Jacob Karlsson <jacob.karlsson95@gmail.com>
Co-committed-by: Jacob Karlsson <jacob.karlsson95@gmail.com>
2024-06-01 16:21:00 +00:00
Jacob Karlsson eefe93820d 1.0.2 2024-05-17 17:39:03 +02:00
Jacob Karlsson 1d2470865b Improve cb types 2024-05-17 17:38:54 +02:00
Jacob Karlsson 8e6128f238 Rename to pzp (#4)
For https://codeberg.org/pzp/pzp-sdk/issues/1

Reviewed-on: https://codeberg.org/pzp/pzp-db/pulls/4
Co-authored-by: Jacob Karlsson <jacob.karlsson95@gmail.com>
Co-committed-by: Jacob Karlsson <jacob.karlsson95@gmail.com>
2024-04-27 21:47:50 +00:00
40 changed files with 308 additions and 255 deletions

View File

@ -1,19 +1,18 @@
# ppppp-db
# pzp-db
The message database for ppppp.
The message database for PZP.
## Installation
We're not on npm yet. In your package.json, include this as
```js
"ppppp-db": "github:staltz/ppppp-db"
```
npm install pzp-db
```
## Usage
It's a secret-stack plugin much like ssb-db2. Other than that, you can also use
the feed format `const FeedV1 = require('ppppp-db/feed-v1')`.
the feed format `const FeedV1 = require('pzp-db/feed-v1')`.
You can use it like
@ -21,15 +20,15 @@ You can use it like
const p = require('node:util').promisify
const keypair = Keypair.generate('ed25519', 'alice')
const DIR = path.join(os.tmpdir(), 'ppppp-db-temp')
const DIR = path.join(os.tmpdir(), 'pzp-db-temp')
const pzp = require('secret-stack/bare')()
.use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack'))
.use(require('ppppp-db'))
.use(require('pzp-db'))
.use(require('ssb-box'))
.call(null, {
shse: { caps: require('ppppp-caps')
shse: { caps: require('pzp-caps')
},
global: {
keypair,

158
lib/db-tangle.js Normal file
View File

@ -0,0 +1,158 @@
const pull = require('pull-stream')
const p = require('node:util').promisify
const MsgV4 = require('./msg-v4')
/**
* @typedef {string} MsgID
* @typedef {import('./msg-v4').Msg} Msg
*/
/**
* @typedef {{
* id?: never;
* msg?: never;
* received?: never;
* }} RecDeleted
*
* @typedef {{
* id: MsgID;
* msg: Msg;
* received: number;
* }} RecPresent
*
* @typedef {RecPresent | RecDeleted} Rec
*/
/**
* @template T
* @typedef {[T] extends [void] ?
* (...args: [Error] | []) => void :
* (...args: [Error] | [null, T]) => void
* } CB
*/
class DBTangle extends MsgV4.Tangle {
/** @type {(msgID: MsgID, cb: CB<Msg>) => void} */
#getMsg
/**
* @param {MsgID} rootID
* @param {(msgID: MsgID, cb: CB<Msg>) => void} getMsg
*/
constructor(rootID, getMsg) {
super(rootID)
this.#getMsg = getMsg
}
/**
* @param {MsgID} rootID
* @param {AsyncIterable<Rec>} recordsIter
* @param {(msgID: MsgID, cb: any) => void} getMsg
* @return {Promise<DBTangle>}
*/
static async init(rootID, recordsIter, getMsg) {
const dbtangle = new DBTangle(rootID, getMsg)
for await (const rec of recordsIter) {
if (!rec.msg) continue
dbtangle.add(rec.id, rec.msg)
}
return dbtangle
}
/**
* Given a set of msgs (`msgIDs`) in this tangle, find all "deletable" and
* "erasable" msgs that precede that set.
*
* *Deletables* are msgs that precede `msgsIDs` but are not important in any
* validation path toward the root, and thus can be deleted.
*
* *Erasables* are msgs that precede `msgsIDs` and can be erased without
* losing a validation path toward the root.
* @param {Array<MsgID>} msgIDs
* @returns {{ deletables: Set<MsgID>, erasables: Set<MsgID> } | null}
*/
getDeletablesAndErasables(...msgIDs) {
// Determine erasables
const erasables = new Set()
const minimum = this.getMinimumAmong(msgIDs)
for (const msgID of minimum) {
const trail = this.shortestPathToRoot(msgID)
if (!trail) return null
for (const id of trail) {
erasables.add(id)
}
}
// Determine deletables
const deletables = new Set()
const sorted = this.topoSort()
for (const msgID of sorted) {
if (erasables.has(msgID)) continue
if (minimum.some((min) => this.precedes(msgID, min))) {
deletables.add(msgID)
}
}
return { deletables, erasables }
}
/**
* @param {Array<string>=} minSet
* @param {Array<string>=} maxSet
* @param {CB<Array<Msg>>=} cb
* @return {Promise<Array<Msg>>|void}
*/
slice(minSet = [], maxSet = [], cb) {
// @ts-ignore
if (cb === undefined) return p(this.slice).bind(this)(minSet, maxSet)
const minSetGood = minSet.filter((msgID) => this.has(msgID))
const maxSetGood = maxSet.filter((msgID) => this.has(msgID))
const minSetTight = this.getMinimumAmong(minSetGood)
const trail = new Set()
for (const msgID of minSetTight) {
const path = this.shortestPathToRoot(msgID)
if (!path) return cb(Error("Couldn't get shortest path to root when slicing dbtangle"))
for (const msgID of path) {
trail.add(msgID)
}
}
const msgs = /**@type {Array<Msg>}*/ ([])
pull(
pull.values(this.topoSort()),
pull.asyncMap((msgID, cb) => {
this.#getMsg(msgID, (err, msg) => {
if (err) return cb(err)
cb(null, { id: msgID, msg })
})
}),
pull.drain(
(rec) => {
if (trail.has(rec.id)) {
if (rec.msg) msgs.push({ ...rec.msg, data: null })
}
const isMin = minSetGood.includes(rec.id)
const isMax = maxSetGood.includes(rec.id)
const isBeforeMin = minSetGood.some((min) =>
this.precedes(rec.id, min)
)
const isAfterMax = maxSetGood.some((max) =>
this.precedes(max, rec.id)
)
if (!isMin && isBeforeMin) return
if (!isMax && isAfterMax) return
if (rec.msg) msgs.push(rec.msg)
},
(err) => {
if (err) return cb(Error('DBTangle.slice() failed', { cause: err }))
return cb(null, msgs)
}
)
)
}
}
module.exports = DBTangle

View File

@ -7,7 +7,7 @@ const MsgV4 = require('./msg-v4')
* @typedef {import('./index').RecPresent} RecPresent
* @typedef {import('./index').Rec} Rec
* @typedef {import('./index').Misc} Misc
* @typedef {import('ppppp-keypair').Keypair} Keypair
* @typedef {import('pzp-keypair').Keypair} Keypair
*
* @typedef {Buffer | Uint8Array} B4A
*

View File

@ -3,11 +3,12 @@ const promisify = require('promisify-4loc')
const b4a = require('b4a')
const base58 = require('bs58')
const Obz = require('obz')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const pull = require('pull-stream')
const p = require('node:util').promisify
const Log = require('./log')
const MsgV4 = require('./msg-v4')
const DBTangle = require('./db-tangle')
const {
SIGNATURE_TAG_ACCOUNT_ADD,
ACCOUNT_SELF,
@ -19,9 +20,9 @@ const Ghosts = require('./ghosts')
const { decrypt } = require('./encryption')
/**
* @typedef {import('ppppp-keypair').Keypair} Keypair
* @typedef {import('ppppp-keypair').KeypairPublicSlice} KeypairPublicSlice
* @typedef {import('ppppp-keypair').KeypairPrivateSlice} KeypairPrivateSlice
* @typedef {import('pzp-keypair').Keypair} Keypair
* @typedef {import('pzp-keypair').KeypairPublicSlice} KeypairPublicSlice
* @typedef {import('pzp-keypair').KeypairPrivateSlice} KeypairPrivateSlice
* @typedef {string} MsgID
* @typedef {import('./msg-v4').Msg} Msg
* @typedef {import('./msg-v4').AccountData} AccountData
@ -72,7 +73,7 @@ const { decrypt } = require('./encryption')
/**
* @template T
* @typedef {T extends void ?
* @typedef {[T] extends [void] ?
* (...args: [Error] | []) => void :
* (...args: [Error] | [null, T]) => void
* } CB
@ -93,128 +94,6 @@ function assertValidConfig(config) {
}
}
class DBTangle extends MsgV4.Tangle {
/** @type {(msgID: MsgID, cb: CB<Msg>) => void} */
#getMsg
/**
* @param {MsgID} rootID
* @param {(msgID: MsgID, cb: CB<Msg>) => void} getMsg
*/
constructor(rootID, getMsg) {
super(rootID)
this.#getMsg = getMsg
}
/**
* @param {MsgID} rootID
* @param {AsyncIterable<Rec>} recordsIter
* @param {(msgID: MsgID, cb: any) => void} getMsg
* @return {Promise<DBTangle>}
*/
static async init(rootID, recordsIter, getMsg) {
const dbtangle = new DBTangle(rootID, getMsg)
for await (const rec of recordsIter) {
if (!rec.msg) continue
dbtangle.add(rec.id, rec.msg)
}
return dbtangle
}
/**
* Given a set of msgs (`msgIDs`) in this tangle, find all "deletable" and
* "erasable" msgs that precede that set.
*
* *Deletables* are msgs that precede `msgsIDs` but are not important in any
* validation path toward the root, and thus can be deleted.
*
* *Erasables* are msgs that precede `msgsIDs` and can be erased without
* losing a validation path toward the root.
* @param {Array<MsgID>} msgIDs
* @returns {{ deletables: Set<MsgID>, erasables: Set<MsgID> }}
*/
getDeletablesAndErasables(...msgIDs) {
// Determine erasables
const erasables = new Set()
const minimum = this.getMinimumAmong(msgIDs)
for (const msgID of minimum) {
const trail = this.shortestPathToRoot(msgID)
for (const id of trail) {
erasables.add(id)
}
}
// Determine deletables
const deletables = new Set()
const sorted = this.topoSort()
for (const msgID of sorted) {
if (erasables.has(msgID)) continue
if (minimum.some((min) => this.precedes(msgID, min))) {
deletables.add(msgID)
}
}
return { deletables, erasables }
}
/**
* @param {Array<string>=} minSet
* @param {Array<string>=} maxSet
* @param {CB<Array<Msg>>=} cb
* @return {Promise<Array<Msg>>|void}
*/
slice(minSet = [], maxSet = [], cb) {
// @ts-ignore
if (cb === undefined) return p(this.slice).bind(this)(minSet, maxSet)
const minSetGood = minSet.filter((msgID) => this.has(msgID))
const maxSetGood = maxSet.filter((msgID) => this.has(msgID))
const minSetTight = this.getMinimumAmong(minSetGood)
const trail = new Set()
for (const msgID of minSetTight) {
const path = this.shortestPathToRoot(msgID)
for (const msgID of path) {
trail.add(msgID)
}
}
const msgs = /**@type {Array<Msg>}*/ ([])
pull(
pull.values(this.topoSort()),
pull.asyncMap((msgID, cb) => {
this.#getMsg(msgID, (err, msg) => {
if (err) return cb(err)
cb(null, { id: msgID, msg })
})
}),
pull.drain(
(rec) => {
if (trail.has(rec.id)) {
if (rec.msg) msgs.push({ ...rec.msg, data: null })
}
const isMin = minSetGood.includes(rec.id)
const isMax = maxSetGood.includes(rec.id)
const isBeforeMin = minSetGood.some((min) =>
this.precedes(rec.id, min)
)
const isAfterMax = maxSetGood.some((max) =>
this.precedes(max, rec.id)
)
if (!isMin && isBeforeMin) return
if (!isMax && isAfterMax) return
if (rec.msg) msgs.push(rec.msg)
},
(err) => {
if (err) return cb(Error('DBTangle.slice() failed', { cause: err }))
return cb(null, msgs)
}
)
)
}
}
/**
* @param {Peer} peer
* @param {Config} config
@ -421,7 +300,7 @@ function initDB(peer, config) {
/**
* @param {Pick<RecPresent, 'id' | 'msg'>} rec
* @param {(err: Error | null, tangle: DBTangle | null) => void} cb
* @param {CB<DBTangle | null>} cb
*/
function getAccountTangle(rec, cb) {
const accountID = getAccountID(rec)
@ -432,8 +311,7 @@ function initDB(peer, config) {
}
if (!accountTangle.has(accountID)) {
return cb(
Error(`Account tangle "${accountID}" is locally unknown`),
null
Error(`Account tangle "${accountID}" is locally unknown`)
)
}
return cb(null, accountTangle)
@ -483,7 +361,7 @@ function initDB(peer, config) {
}
/**
* @param {CB<void>} cb
* @param {CB<void> | void} cb
*/
function loaded(cb) {
if (cb === void 0) return promisify(loaded)()
@ -499,7 +377,7 @@ function initDB(peer, config) {
*
* @param {Pick<RecPresent, 'id' | 'msg'>} rec
* @param {MsgID} tangleID
* @param {(err: Error | null, val: null) => void} cb
* @param {CB<void>} cb
*/
function verifyRec(rec, tangleID, cb) {
let err
@ -515,34 +393,35 @@ function initDB(peer, config) {
if (
(err = MsgV4.validate(rec.msg, tangle, sigkeys, rec.id, tangleID))
) {
return cb(Error('Invalid msg', { cause: err }), null)
return cb(Error('Invalid msg', { cause: err }))
}
return cb(null, null)
return cb()
}
// Identify the account and its sigkeys:
getAccountTangle(rec, (err, accountTangle) => {
// prettier-ignore
if (err) return cb(Error('Unknown account tangle owning this msg', { cause: err }), null)
if (err) return cb(Error('Unknown account tangle owning this msg', { cause: err }))
getSigkeysInAccount(
accountTangle,
rec.msg.metadata.accountTips,
(err, sigkeys) => {
if (err) return cb(err, null)
if (err) return cb(err)
// TODO: how is sigkeys able to be undefined here? typechecker why are you weird?
if (!sigkeys) return cb(Error("Sigkeys missing somehow"))
// Don't accept ghosts to come back, unless they are trail msgs
if (!!rec.msg.data && ghosts.read(tangleID).has(rec.id)) {
return cb(Error('Refusing a ghost msg to come back'), null)
return cb(Error('Refusing a ghost msg to come back'))
}
if (
(err = MsgV4.validate(rec.msg, tangle, sigkeys, rec.id, tangleID))
) {
return cb(Error('Invalid msg', { cause: err }), null)
const valErr = MsgV4.validate(rec.msg, tangle, sigkeys, rec.id, tangleID)
if (valErr) {
return cb(Error('Invalid msg', { cause: valErr }))
}
/** @param {(err: Error | null, val: null) => void} cb */
/** @param {CB<void>} cb */
function verifyInner(cb) {
// Unwrap encrypted inner msg and verify it too
if (typeof rec.msg.data === 'string') {
@ -554,15 +433,15 @@ function initDB(peer, config) {
verifyRec(innerRec, innerMsgID, (err) => {
// prettier-ignore
if (err) return cb(Error('Failed to verify inner msg', { cause: err }), null)
if (err) return cb(Error('Failed to verify inner msg', { cause: err }))
return cb(null, null)
return cb()
})
} else {
return cb(null, null)
return cb()
}
} else {
return cb(null, null)
return cb()
}
}
@ -571,7 +450,7 @@ function initDB(peer, config) {
const validAccountTangle = /** @type {Tangle} */ (accountTangle)
validateAccountMsg(rec.msg, validAccountTangle, (err) => {
if (err)
return cb(Error('Invalid account msg', { cause: err }), null)
return cb(Error('Invalid account msg', { cause: err }))
return verifyInner(cb)
})
} else {
@ -677,7 +556,7 @@ function initDB(peer, config) {
/**
* @param {Msg} msg
* @param {Tangle} accountTangle
* @param {(err: Error | null, val: null) => void} cb
* @param {CB<void>} cb
*/
function validateAccountMsg(msg, accountTangle, cb) {
if (!MsgV4.isRoot(msg)) {
@ -690,21 +569,21 @@ function initDB(peer, config) {
public: msg.sigkey,
}
getAccountPowers(accountTangle, keypair, (err, powers) => {
if (err) return cb(err, null)
if (err) return cb(err)
if (!powers.has('add')) {
// prettier-ignore
return cb(Error(`invalid account msg: sigkey "${msg.sigkey}" does not have "add" power`), null)
return cb(Error(`invalid account msg: sigkey "${msg.sigkey}" does not have "add" power`))
}
return cb(null, null)
return cb()
})
} else {
return cb(null, null)
return cb()
}
// TODO validate 'del'
} else {
return cb(null, null)
return cb()
}
}
@ -800,7 +679,7 @@ function initDB(peer, config) {
* keypair?: KeypairPublicSlice;
* account: string;
* }} opts
* @param {(err: Error | null, has: boolean | null) => void} cb
* @param {CB<boolean>} cb
*/
function accountHas(opts, cb) {
const keypair = opts?.keypair ?? config.global.keypair
@ -811,7 +690,7 @@ function initDB(peer, config) {
pull.asyncMap((msgID, cb) => {
get(msgID, (err, msg) => {
// prettier-ignore
if (err) return cb(Error("db.account.has() failed to get() account tangle message", { cause: err }), null)
if (err) return cb(Error("db.account.has() failed to get() account tangle message", { cause: err }))
if (!msg?.data) return cb(null, false)
/** @type {AccountData} */
@ -826,7 +705,7 @@ function initDB(peer, config) {
}),
pull.collect((err, results) => {
// prettier-ignore
if (err) return cb(Error('db.account.has() failed to calculate', { cause: err }), null)
if (err) return cb(Error('db.account.has() failed to calculate', { cause: err }))
return cb(
null,
@ -897,7 +776,6 @@ function initDB(peer, config) {
})
}
//* @param {(err: Error | null, val: Set<any> | null) => void} cb
/**
* @param {Tangle} accountTangle
* @param {KeypairPublicSlice} keypair
@ -1237,7 +1115,7 @@ function initDB(peer, config) {
...fullOpts,
recps: recps.map(
(recp) =>
// TODO: temporary until our encryption formats are ppppp not SSB
// TODO: temporary until our encryption formats are pzp not SSB
`@${b4a.from(base58.decode(recp)).toString('base64')}.ed25519`
),
}
@ -1286,7 +1164,7 @@ function initDB(peer, config) {
/**
* @param {string} accountId
* @param {string} domain
* @param {(err: Error | null, rec: RecPresent | null) => void} cb
* @param {CB<RecPresent | null>} cb
*/
function findMoot(accountId, domain, cb) {
const findAccount = MsgV4.stripAccount(accountId)
@ -1303,13 +1181,13 @@ function initDB(peer, config) {
/**
* @param {MsgID} msgID
* @param {(err: Error | null, rec: RecPresent | null) => void} cb
* @param {CB<RecPresent | null>} cb
*/
function getRecord(msgID, cb) {
// TODO: improve performance of this when getting many messages, the arg
// could be an array of hashes, so we can do a single pass over the records.
rescanning.onDone(() => {
const isUri = msgID.startsWith('ppppp:')
const isUri = msgID.startsWith('pzp:')
for (let i = 0; i < recs.length; i++) {
const rec = recs[i]
if (!rec) continue
@ -1322,7 +1200,7 @@ function initDB(peer, config) {
/**
* @param {MsgID} msgID
* @param {(err: Error | null, msg?: Msg) => void} cb
* @param {CB<Msg | undefined>} cb
*/
function get(msgID, cb) {
getRecord(msgID, (err, rec) => {
@ -1466,7 +1344,7 @@ function initDB(peer, config) {
/**
* @param {MsgID} tangleID
* @param {(err: Error | null, tangle: DBTangle | null) => void} cb
* @param {CB<DBTangle | null>} cb
*/
function getTangle(tangleID, cb) {
DBTangle.init(tangleID, records(), get).then((tangle) => {

View File

@ -8,7 +8,7 @@ const Cache = require('@alloc/quick-lru') // @ts-ignore
const RAF = require('polyraf') // @ts-ignore
const debounce = require('lodash.debounce') // @ts-ignore
const isBufferZero = require('is-buffer-zero') // @ts-ignore
const debug = require('debug')('ppppp-db:log')
const debug = require('debug')('pzp-db:log')
const {
deletedRecordErr,

View File

@ -25,7 +25,7 @@ function getMsgHashBuf(msg) {
*/
function getMsgID(x) {
if (typeof x === 'string') {
if (x.startsWith('ppppp:message/v4/')) {
if (x.startsWith('pzp:message/v4/')) {
const msgUri = x
const parts = msgUri.split('/')
return parts[parts.length - 1]
@ -48,9 +48,9 @@ function getMsgURI(msg) {
const { account, domain } = msg.metadata
const msgHash = getMsgID(msg)
if (domain) {
return `ppppp:message/v4/${account}/${domain}/${msgHash}`
return `pzp:message/v4/${account}/${domain}/${msgHash}`
} else {
return `ppppp:message/v4/${account}/${msgHash}`
return `pzp:message/v4/${account}/${msgHash}`
}
}

View File

@ -3,7 +3,7 @@ const base58 = require('bs58')
const b4a = require('b4a')
// @ts-ignore
const stringify = require('json-canon')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
// @ts-ignore
const union = require('set.prototype.union')
const { stripAccount } = require('./strip')
@ -26,7 +26,7 @@ const {
const { isEmptyObject } = require('./util')
/**
* @typedef {import('ppppp-keypair').Keypair} Keypair
* @typedef {import('pzp-keypair').Keypair} Keypair
*/
/**

View File

@ -7,8 +7,8 @@
* @returns {string}
*/
function stripAccount(accountId) {
if (accountId.startsWith('ppppp:account/v4/') === false) return accountId
const withoutPrefix = accountId.replace('ppppp:account/v4/', '')
if (accountId.startsWith('pzp:account/v4/') === false) return accountId
const withoutPrefix = accountId.replace('pzp:account/v4/', '')
return withoutPrefix.split('/')[0]
}

View File

@ -222,11 +222,12 @@ class Tangle {
}
/**
* @returns {'feed' | 'account' | 'weave'}
* @returns {'feed' | 'account' | 'weave' | null}
*/
get type() {
if (!this.#rootMsg) {
throw new Error(`Tangle "${this.#rootID}" is missing root message`)
console.trace(new Error(`Tangle "${this.#rootID}" is missing root message`))
return null
}
if (this.#isFeed()) return 'feed'
if (this.#rootMsg.metadata.account === 'self') return 'account'
@ -235,7 +236,8 @@ class Tangle {
get root() {
if (!this.#rootMsg) {
throw new Error(`Tangle "${this.#rootID}" is missing root message`)
console.trace(new Error(`Tangle "${this.#rootID}" is missing root message`))
return null
}
return this.#rootMsg
}
@ -256,7 +258,8 @@ class Tangle {
if (!prev) break
if (prev === lastPrev) {
// prettier-ignore
throw new Error(`Tangle "${this.#rootID}" has a cycle or lacking a trail to root`)
console.trace(new Error(`Tangle "${this.#rootID}" has a cycle or lacking a trail to root`))
return null
} else {
lastPrev = prev
}

View File

@ -1,6 +1,6 @@
const b4a = require('b4a')
const base58 = require('bs58')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
// @ts-ignore
const stringify = require('json-canon')
const Tangle = require('./tangle')
@ -55,6 +55,7 @@ function validateShape(msg) {
if (typeof msg.sig !== 'string') {
return 'invalid msg: must have sig\n' + JSON.stringify(msg)
}
return undefined
}
/**
@ -77,6 +78,7 @@ function validateSigkey(msg) {
// prettier-ignore
return `invalid msg: sigkey "${sigkey}" should have been a base58 string\n` + JSON.stringify(msg)
}
return undefined
}
/**
@ -105,7 +107,10 @@ function validateSigkeyAndAccount(msg, tangle, sigkeys) {
// prettier-ignore
return `invalid msg: accountTips "${msg.metadata.accountTips}" should have been null in an account tangle\n` + JSON.stringify(msg)
}
} else if (tangle.type === null) {
return "Cannot validate tangle of unknown type"
}
return undefined
}
/**
@ -122,6 +127,7 @@ function validateMsgID(str) {
} catch (err) {
return `invalid msgID "${str}": should have been a base58 string`
}
return undefined
}
/**
@ -158,6 +164,7 @@ function validateSignature(msg) {
if (!verified) {
return 'invalid msg: sig is invalid\n' + JSON.stringify(msg)
}
return undefined
}
/**
@ -194,6 +201,8 @@ function validateTangle(msg, tangle, tangleID) {
// prettier-ignore
return `invalid msg: account "${msg.metadata.account}" should have been feed account "${account}"\n` + JSON.stringify(msg)
}
} else if (tangle.type === null) {
return "Unknown tangle type"
}
let lastPrev = null
let minDiff = Infinity
@ -203,7 +212,7 @@ function validateTangle(msg, tangle, tangleID) {
// prettier-ignore
return `invalid msg: prev item "${p}" should have been a string\n` + JSON.stringify(msg)
}
if (p.startsWith('ppppp:')) {
if (p.startsWith('pzp:')) {
// prettier-ignore
return `invalid msg: prev item "${p}" is a URI, but should have been a hash\n` + JSON.stringify(msg)
}
@ -241,6 +250,7 @@ function validateTangle(msg, tangle, tangleID) {
// prettier-ignore
return `invalid msg: depth must be the largest prev depth plus one\n` + JSON.stringify(msg)
}
return undefined
}
/**
@ -257,6 +267,7 @@ function validateTangleRoot(msg, msgID, tangleID) {
// prettier-ignore
return `invalid msg: tangle root "${tangleID}" must not have self tangle data\n` + JSON.stringify(msg)
}
return undefined
}
/**
@ -277,6 +288,7 @@ function validateDomain(domain) {
// prettier-ignore
return `invalid domain: "${domain}" contains characters other than a-z, A-Z, 0-9, or _`
}
return undefined
}
/**
@ -295,6 +307,7 @@ function validateData(msg) {
// prettier-ignore
return `invalid msg: data "${data}" must be an object or a string` + JSON.stringify(msg)
}
return undefined
}
/**
@ -317,6 +330,7 @@ function validateDataSizeHash(msg) {
// prettier-ignore
return `invalid msg: metadata.dataSize ${actualSize} should have been "${expectedSize}"\n` + JSON.stringify(msg)
}
return undefined
}
/**
@ -332,11 +346,8 @@ function validate(msg, tangle, sigkeys, msgID, rootID) {
if ((err = validateSigkey(msg))) return err
if ((err = validateData(msg))) return err
try {
if (tangle.type === 'feed' && isMoot(msg)) return // nothing else to check
} catch (/** @type {any} */ err) {
return err
}
if (tangle.type === 'feed' && isMoot(msg)) return // nothing else to check
if (tangle.type === null) return "Missing tangle type when validating msg"
if ((err = validateDataSizeHash(msg))) return err
if ((err = validateDomain(msg.metadata.domain))) return err
@ -347,6 +358,7 @@ function validate(msg, tangle, sigkeys, msgID, rootID) {
if ((err = validateTangle(msg, tangle, rootID))) return err
}
if ((err = validateSignature(msg))) return err
return undefined
}
module.exports = {

View File

@ -1,11 +1,11 @@
{
"name": "ppppp-db",
"version": "0.0.1",
"description": "Default ppppp database",
"homepage": "https://github.com/staltz/ppppp-db",
"name": "pzp-db",
"version": "1.0.4",
"description": "Default PZP database",
"homepage": "https://codeberg.org/pzp/pzp-db",
"repository": {
"type": "git",
"url": "git@github.com:staltz/ppppp-db.git"
"url": "git@codeberg.org:pzp/pzp-db.git"
},
"author": "Andre Staltz <contact@staltz.com>",
"license": "MIT",
@ -24,6 +24,9 @@
},
"./msg-v4": {
"require": "./lib/msg-v4/index.js"
},
"./db-tangle": {
"require": "./lib/db-tangle.js"
}
},
"dependencies": {
@ -40,7 +43,7 @@
"mutexify": "~1.4.0",
"obz": "~1.1.0",
"polyraf": "^1.1.0",
"ppppp-keypair": "github:staltz/ppppp-keypair#61ef4420578f450dc2cc7b1efc1c5a691a871c74",
"pzp-keypair": "^1.0.0",
"promisify-4loc": "~1.0.0",
"promisify-tuple": "~1.2.0",
"pull-stream": "^3.7.0",
@ -53,7 +56,7 @@
"c8": "^7.11.0",
"flumecodec": "~0.0.1",
"husky": "^4.3.0",
"ppppp-caps": "github:staltz/ppppp-caps#93fa810b9a40b78aef4872d4c2a8412cccb52929",
"pzp-caps": "^1.0.0",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",
"rimraf": "^4.4.0",

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const p = require('node:util').promisify
const os = require('node:os')
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const MsgV4 = require('../lib/msg-v4')
const DIR = path.join(os.tmpdir(), 'ppppp-db-account-add')
const DIR = path.join(os.tmpdir(), 'pzp-db-account-add')
rimraf.sync(DIR)
test('account.add()', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-account-create')
const DIR = path.join(os.tmpdir(), 'pzp-db-account-create')
rimraf.sync(DIR)
test('account.create() ', async (t) => {

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../lib/msg-v4')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-add')
const DIR = path.join(os.tmpdir(), 'pzp-db-add')
rimraf.sync(DIR)
test('add()', async (t) => {

View File

@ -5,10 +5,10 @@ const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Log = require('../lib/log')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-del')
const DIR = path.join(os.tmpdir(), 'pzp-db-del')
rimraf.sync(DIR)
test('del()', async (t) => {

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const Log = require('../lib/log')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-erase')
const DIR = path.join(os.tmpdir(), 'pzp-db-erase')
rimraf.sync(DIR)
test('erase()', async (t) => {

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../lib/msg-v4')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-feed-find-moot')
const DIR = path.join(os.tmpdir(), 'pzp-db-feed-find-moot')
rimraf.sync(DIR)
test('feed.findMoot()', async (t) => {

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../lib/msg-v4')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-feed-get-id')
const DIR = path.join(os.tmpdir(), 'pzp-db-feed-get-id')
rimraf.sync(DIR)
test('feed.getID()', async (t) => {

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../lib/msg-v4')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-feed-publish')
const DIR = path.join(os.tmpdir(), 'pzp-db-feed-publish')
rimraf.sync(DIR)
test('feed.publish()', async (t) => {

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../lib/msg-v4')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-get')
const DIR = path.join(os.tmpdir(), 'pzp-db-get')
rimraf.sync(DIR)
test('get()', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle')
const DIR = path.join(os.tmpdir(), 'pzp-db-tangle')
rimraf.sync(DIR)
/**

View File

@ -4,11 +4,11 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const MsgV4 = require('../lib/msg-v4')
const DIR = path.join(os.tmpdir(), 'ppppp-db-ghosts')
const DIR = path.join(os.tmpdir(), 'pzp-db-ghosts')
rimraf.sync(DIR)
const keypair = Keypair.generate('ed25519', 'alice')

View File

@ -6,7 +6,7 @@ const Log = require('../../lib/log')
test('Log basics', async function (t) {
await t.test('Log handles basic binary records', async function (t) {
const file = '/tmp/ppppp-db-log-test-basic-binary.log'
const file = '/tmp/pzp-db-log-test-basic-binary.log'
try {
fs.unlinkSync(file)
} catch (_) {}
@ -34,7 +34,7 @@ test('Log basics', async function (t) {
const json2 = { test: 'testing2' }
await t.test('Log handles basic json records', async function (t) {
const file = '/tmp/ppppp-db-log-test-basic-json.log'
const file = '/tmp/pzp-db-log-test-basic-json.log'
try {
fs.unlinkSync(file)
} catch (_) {}
@ -59,7 +59,7 @@ test('Log basics', async function (t) {
})
await t.test('Log handles basic json record re-reading', async function (t) {
const file = '/tmp/ppppp-db-log-test-basic-json.log'
const file = '/tmp/pzp-db-log-test-basic-json.log'
const log = Log(file, {
blockSize: 2 * 1024,
codec: require('flumecodec/json'),

View File

@ -5,7 +5,7 @@ const Log = require('../../lib/log')
test('Log compaction', async (t) => {
await t.test('compact a log that does not have holes', async (t) => {
const file = '/tmp/ppppp-db-log-compaction-test-' + Date.now() + '.log'
const file = '/tmp/pzp-db-log-compaction-test-' + Date.now() + '.log'
const log = Log(file, { blockSize: 15 })
const stats = await p(log.stats)()
@ -62,7 +62,7 @@ test('Log compaction', async (t) => {
})
await t.test('delete first record, compact, stream', async (t) => {
const file = '/tmp/ppppp-db-log-compaction-test-' + Date.now() + '.log'
const file = '/tmp/pzp-db-log-compaction-test-' + Date.now() + '.log'
const log = Log(file, { blockSize: 15 })
const buf1 = Buffer.from('first')
@ -119,7 +119,7 @@ test('Log compaction', async (t) => {
})
await t.test('delete last record, compact, stream', async (t) => {
const file = '/tmp/ppppp-db-log-compaction-test-' + Date.now() + '.log'
const file = '/tmp/pzp-db-log-compaction-test-' + Date.now() + '.log'
const log = Log(file, { blockSize: 15 })
const buf1 = Buffer.from('first')

View File

@ -15,7 +15,7 @@ function decode(buf) {
}
test('Log handles corrupted records', async (t) => {
const file = '/tmp/ppppp-db-log-corrupt-records.log'
const file = '/tmp/pzp-db-log-corrupt-records.log'
await t.test('Simulate corruption', async (t) => {
try {
@ -93,7 +93,7 @@ test('Log handles corrupted records', async (t) => {
})
test('Log handles corrupted length', async (t) => {
const file = '/tmp/ppppp-db-log-corrupt-length.log'
const file = '/tmp/pzp-db-log-corrupt-length.log'
await t.test('Simulate length corruption', async (t) => {
try {

View File

@ -16,7 +16,7 @@ const msg3 = Buffer.from(
test('Log deletes', async (t) => {
await t.test('Simple delete', async (t) => {
const file = '/tmp/ppppp-db-log-test-del.log'
const file = '/tmp/pzp-db-log-test-del.log'
try {
fs.unlinkSync(file)
} catch (_) {}
@ -49,7 +49,7 @@ test('Log deletes', async (t) => {
})
await t.test('Deleted records are not invalid upon re-opening', async (t) => {
const file = '/tmp/ppppp-db-log-test-del-invalid.log'
const file = '/tmp/pzp-db-log-test-del-invalid.log'
try {
fs.unlinkSync(file)
} catch (_) {}

View File

@ -9,7 +9,7 @@ const msg2 = Buffer.from('ola mundo ola mundo ola mundo')
test('Log overwrites', async (t) => {
await t.test('Simple overwrite', async (t) => {
const file = '/tmp/ppppp-db-log-test-overwrite.log'
const file = '/tmp/pzp-db-log-test-overwrite.log'
try {
fs.unlinkSync(file)
} catch (_) {}
@ -49,7 +49,7 @@ test('Log overwrites', async (t) => {
})
await t.test('Cannot overwrite larger data', async (t) => {
const file = '/tmp/ppppp-db-log-test-overwrite-larger.log'
const file = '/tmp/pzp-db-log-test-overwrite-larger.log'
try {
fs.unlinkSync(file)
} catch (_) {}

View File

@ -1,6 +1,6 @@
const test = require('node:test')
const assert = require('node:assert')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../../lib/msg-v4')
let account

View File

@ -1,6 +1,6 @@
const test = require('node:test')
const assert = require('node:assert')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../../lib/msg-v4')
test('MsgV4 domain validation', async (t) => {

View File

@ -1,7 +1,7 @@
const test = require('node:test')
const assert = require('node:assert')
const base58 = require('bs58')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../../lib/msg-v4')
const keypair = Keypair.generate('ed25519', 'alice')
@ -121,7 +121,7 @@ test('MsgV4 tangles prev validation', async (t) => {
})
const msgID2 = MsgV4.getMsgID(msg2)
const randBuf = Buffer.alloc(16).fill(16)
const fakeMsgKey1 = `ppppp:message/v4/${base58.encode(randBuf)}`
const fakeMsgKey1 = `pzp:message/v4/${base58.encode(randBuf)}`
msg2.metadata.tangles[mootID].depth = 1
msg2.metadata.tangles[mootID].prev = [fakeMsgKey1]

View File

@ -1,6 +1,6 @@
const test = require('node:test')
const assert = require('node:assert')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../../lib/msg-v4')
test('MsgV4 lipmaa prevs', (t) => {

View File

@ -1,6 +1,6 @@
const test = require('node:test')
const assert = require('node:assert')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../../lib/msg-v4')
test('MsgV4.Tangle simple multi-author tangle', (t) => {

View File

@ -1,6 +1,6 @@
const test = require('node:test')
const assert = require('node:assert')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const MsgV4 = require('../../lib/msg-v4')
test('MsgV4 validation', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-msgs-iter')
const DIR = path.join(os.tmpdir(), 'pzp-db-msgs-iter')
rimraf.sync(DIR)
test('msgs() iterator', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-on-msg-added')
const DIR = path.join(os.tmpdir(), 'pzp-db-on-msg-added')
rimraf.sync(DIR)
test('onRecordAdded', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-on-record-deleted-or-erased')
const DIR = path.join(os.tmpdir(), 'pzp-db-on-record-deleted-or-erased')
rimraf.sync(DIR)
test('onRecordDeletedOrErased()', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-re-open')
const DIR = path.join(os.tmpdir(), 'pzp-db-re-open')
rimraf.sync(DIR)
test('publish some msgs, close, re-open', async (t) => {

View File

@ -4,10 +4,10 @@ const path = require('node:path')
const os = require('node:os')
const p = require('node:util').promisify
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const DIR = path.join(os.tmpdir(), 'ppppp-db-records-iter')
const DIR = path.join(os.tmpdir(), 'pzp-db-records-iter')
rimraf.sync(DIR)
test('records() iterator', async (t) => {

View File

@ -4,12 +4,12 @@ const path = require('node:path')
const p = require('node:util').promisify
const os = require('node:os')
const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair')
const Keypair = require('pzp-keypair')
const { createPeer } = require('./util')
const MsgV4 = require('../lib/msg-v4')
const DIR = path.join(os.tmpdir(), 'ppppp-db-sigkeys')
const DIR2 = path.join(os.tmpdir(), 'ppppp-db-sigkeys2')
const DIR = path.join(os.tmpdir(), 'pzp-db-sigkeys')
const DIR2 = path.join(os.tmpdir(), 'pzp-db-sigkeys2')
rimraf.sync(DIR)
rimraf.sync(DIR2)

View File

@ -4,7 +4,7 @@ function createPeer(globalConfig) {
.use(require('secret-handshake-ext/secret-stack'))
.use(require('../lib'))
.use(require('ssb-box'))
.call(null, { shse: { caps: require('ppppp-caps') }, global: globalConfig })
.call(null, { shse: { caps: require('pzp-caps') }, global: globalConfig })
}
module.exports = {