new API tangle.slice()

This commit is contained in:
Andre Staltz 2023-12-15 14:34:34 +02:00
parent f523df3a0e
commit 99c1520415
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
2 changed files with 105 additions and 9 deletions

View File

@ -86,12 +86,17 @@ function assertValidConfig(config) {
} }
class DBTangle extends MsgV3.Tangle { class DBTangle extends MsgV3.Tangle {
/** @type {(msgID: MsgID) => Msg | undefined} */
#getMsg
/** /**
* @param {MsgID} rootID * @param {MsgID} rootID
* @param {Iterable<Rec>} recordsIter * @param {Iterable<Rec>} recordsIter
* @param {(msgID: MsgID) => Msg | undefined} getMsg
*/ */
constructor(rootID, recordsIter) { constructor(rootID, recordsIter, getMsg) {
super(rootID) super(rootID)
this.#getMsg = getMsg
for (const rec of recordsIter) { for (const rec of recordsIter) {
if (!rec.msg) continue if (!rec.msg) continue
this.add(rec.id, rec.msg) this.add(rec.id, rec.msg)
@ -133,6 +138,42 @@ class DBTangle extends MsgV3.Tangle {
return { deletables, erasables } return { deletables, erasables }
} }
/**
* @param {Array<string>} minSet
* @param {Array<string>} maxSet
* @returns {Array<Msg>}
*/
slice(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>}*/ ([])
for (const msgID of this.topoSort()) {
if (trail.has(msgID)) {
const msg = this.#getMsg(msgID)
if (msg) msgs.push({ ...msg, data: null })
}
const isMin = minSetGood.includes(msgID)
const isMax = maxSetGood.includes(msgID)
const isBeforeMin = minSetGood.some((min) => this.precedes(msgID, min))
const isAfterMax = maxSetGood.some((max) => this.precedes(max, msgID))
if (!isMin && isBeforeMin) continue
if (!isMax && isAfterMax) continue
const msg = this.#getMsg(msgID)
if (msg) msgs.push(msg)
}
return msgs
}
} }
/** /**
@ -288,7 +329,7 @@ function initDB(peer, config) {
/** @type {Record<MsgID, DBTangle>} */ /** @type {Record<MsgID, DBTangle>} */
const tangles = {} const tangles = {}
for (const tangleID of tangleIDs) { for (const tangleID of tangleIDs) {
tangles[tangleID] ??= new DBTangle(tangleID, records()) tangles[tangleID] ??= new DBTangle(tangleID, records(), get)
} }
return tangles return tangles
} }
@ -301,7 +342,7 @@ function initDB(peer, config) {
const accountID = getAccountID(rec) const accountID = getAccountID(rec)
let accountTangle = /** @type {Tangle | null} */ (null) let accountTangle = /** @type {Tangle | null} */ (null)
if (accountID) { if (accountID) {
accountTangle = new DBTangle(accountID, records()) accountTangle = new DBTangle(accountID, records(), get)
if (rec.id === accountID) { if (rec.id === accountID) {
accountTangle.add(rec.id, rec.msg) accountTangle.add(rec.id, rec.msg)
} }
@ -359,7 +400,7 @@ function initDB(peer, config) {
function verifyRec(rec, tangleID) { function verifyRec(rec, tangleID) {
// TODO: optimize this. This may be slow if you're adding many msgs in a // TODO: optimize this. This may be slow if you're adding many msgs in a
// row, because it creates a new Map() each time. Perhaps with QuickLRU // row, because it creates a new Map() each time. Perhaps with QuickLRU
const tangle = new DBTangle(tangleID, records()) const tangle = new DBTangle(tangleID, records(), get)
if (rec.id === tangleID) { if (rec.id === tangleID) {
tangle.add(rec.id, rec.msg) tangle.add(rec.id, rec.msg)
} }
@ -560,7 +601,7 @@ function initDB(peer, config) {
function accountHas(opts) { function accountHas(opts) {
const keypair = opts?.keypair ?? config.keypair const keypair = opts?.keypair ?? config.keypair
const accountTangle = new DBTangle(opts.account, records()) const accountTangle = new DBTangle(opts.account, records(), get)
for (const msgID of accountTangle.topoSort()) { for (const msgID of accountTangle.topoSort()) {
const msg = get(msgID) const msg = get(msgID)
if (!msg?.data) continue if (!msg?.data) continue
@ -731,7 +772,7 @@ function initDB(peer, config) {
} }
// Verify powers of the signingKeypair: // Verify powers of the signingKeypair:
const accountTangle = new DBTangle(opts.account, records()) const accountTangle = new DBTangle(opts.account, records(), get)
if (obeying) { if (obeying) {
const signingPowers = getAccountPowers(accountTangle, signingKeypair) const signingPowers = getAccountPowers(accountTangle, signingKeypair)
if (!signingPowers.has('add')) { if (!signingPowers.has('add')) {
@ -856,7 +897,7 @@ function initDB(peer, config) {
const tangleTemplates = opts.tangles ?? [] const tangleTemplates = opts.tangles ?? []
tangleTemplates.push(mootID) tangleTemplates.push(mootID)
const tangles = populateTangles(tangleTemplates) const tangles = populateTangles(tangleTemplates)
const accountTangle = new DBTangle(opts.account, records()) const accountTangle = new DBTangle(opts.account, records(), get)
const accountTips = [...accountTangle.tips] const accountTips = [...accountTangle.tips]
const fullOpts = { ...opts, tangles, accountTips, keypair } const fullOpts = { ...opts, tangles, accountTips, keypair }
@ -946,6 +987,7 @@ function initDB(peer, config) {
/** /**
* @param {MsgID} msgID * @param {MsgID} msgID
* @returns {Msg | undefined}
*/ */
function get(msgID) { function get(msgID) {
return getRecord(msgID)?.msg return getRecord(msgID)?.msg
@ -1066,7 +1108,7 @@ function initDB(peer, config) {
* @returns {DBTangle} * @returns {DBTangle}
*/ */
function getTangle(tangleID) { function getTangle(tangleID) {
return new DBTangle(tangleID, records()) return new DBTangle(tangleID, records(), get)
} }
function* msgs() { function* msgs() {

View File

@ -11,9 +11,15 @@ const Keypair = require('ppppp-keypair')
const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle') const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle')
rimraf.sync(DIR) rimraf.sync(DIR)
/**
* /-reply1Hi <-\ /--reply3Hi
* root <-< >-reply2 <-<
* \--reply1Lo <-/ \--reply3Lo
*/
test('getTangle()', async (t) => { test('getTangle()', async (t) => {
let peer let peer
let rootPost, reply1Lo, reply1Hi, reply2, reply3Lo, reply3Hi let rootPost, reply1Lo, reply1Hi, reply2, reply3Lo, reply3Hi
let reply1LoText, reply1HiText, reply3LoText, reply3HiText
let tangle let tangle
// Setup // Setup
@ -29,7 +35,10 @@ test('getTangle()', async (t) => {
await peer.db.loaded() await peer.db.loaded()
const id = await p(peer.db.account.create)({ subdomain: 'person' }) const id = await p(peer.db.account.create)({
subdomain: 'person',
_nonce: 'alice',
})
// Slow down append so that we can trigger msg creation in parallel // Slow down append so that we can trigger msg creation in parallel
const originalAppend = peer.db._getLog().append const originalAppend = peer.db._getLog().append
@ -64,6 +73,8 @@ test('getTangle()', async (t) => {
]) ])
reply1Lo = reply1B.localeCompare(reply1C) < 0 ? reply1B : reply1C reply1Lo = reply1B.localeCompare(reply1C) < 0 ? reply1B : reply1C
reply1Hi = reply1B.localeCompare(reply1C) < 0 ? reply1C : reply1B reply1Hi = reply1B.localeCompare(reply1C) < 0 ? reply1C : reply1B
reply1LoText = reply1B.localeCompare(reply1C) < 0 ? 'reply 1B' : 'reply 1C'
reply1HiText = reply1B.localeCompare(reply1C) < 0 ? 'reply 1C' : 'reply 1B'
reply2 = ( reply2 = (
await p(peer.db.feed.publish)({ await p(peer.db.feed.publish)({
@ -93,6 +104,8 @@ test('getTangle()', async (t) => {
]) ])
reply3Lo = reply3B.localeCompare(reply3C) < 0 ? reply3B : reply3C reply3Lo = reply3B.localeCompare(reply3C) < 0 ? reply3B : reply3C
reply3Hi = reply3B.localeCompare(reply3C) < 0 ? reply3C : reply3B reply3Hi = reply3B.localeCompare(reply3C) < 0 ? reply3C : reply3B
reply3LoText = reply3B.localeCompare(reply3C) < 0 ? 'reply 3B' : 'reply 3C'
reply3HiText = reply3B.localeCompare(reply3C) < 0 ? 'reply 3C' : 'reply 3B'
tangle = peer.db.getTangle(rootPost) tangle = peer.db.getTangle(rootPost)
} }
@ -264,6 +277,47 @@ test('getTangle()', async (t) => {
assert.deepEqual(actual4, expected4) assert.deepEqual(actual4, expected4)
}) })
await t.test('Tangle.slice', (t) => {
{
const msgs = tangle.slice([], [reply2])
const texts = msgs.map((msg) => msg.data?.text)
assert.deepEqual(texts, ['root', reply1LoText, reply1HiText, 'reply 2'])
}
{
const msgs = tangle.slice([reply2], [])
const texts = msgs.map((msg) => msg.data?.text)
assert.deepEqual(texts, [
undefined, // root
undefined, // reply1Lo (no need to have a trail from reply1Hi)
'reply 2',
reply3LoText,
reply3HiText,
])
}
{
const msgs = tangle.slice([reply2], [reply2])
const texts = msgs.map((msg) => msg.data?.text)
assert.deepEqual(texts, [
undefined, // root
undefined, // reply1Lo (no need to have a trail from reply1Hi)
'reply 2',
])
}
{
const msgs = tangle.slice([reply2], [reply2, reply3Lo])
const texts = msgs.map((msg) => msg.data?.text)
assert.deepEqual(texts, [
undefined, // root
undefined, // reply1Lo (no need to have a trail from reply1Hi)
'reply 2',
reply3LoText,
])
}
})
await t.test('Tangle.topoSort after some deletes and erases', async (t) => { await t.test('Tangle.topoSort after some deletes and erases', async (t) => {
const { deletables, erasables } = tangle.getDeletablesAndErasables(reply3Lo) const { deletables, erasables } = tangle.getDeletablesAndErasables(reply3Lo)
for (const msgID of deletables) { for (const msgID of deletables) {