correct semantics for getRecordPurpose ghost logic

This commit is contained in:
Andre Staltz 2023-10-19 17:52:11 +03:00
parent ca8f2ceed1
commit ba364192ce
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
3 changed files with 52 additions and 29 deletions

View File

@ -188,9 +188,9 @@ function initGoals(peer, config) {
/** /**
* @public * @public
* @param {RecPresent} rec * @param {RecPresent} rec
* @param {CB<Purpose>} cb * @returns {Purpose}
*/ */
function getRecordPurpose(rec, cb) { function getRecordPurpose(rec) {
assertDBPlugin(peer) assertDBPlugin(peer)
let servesAsTrail = false let servesAsTrail = false
@ -202,13 +202,13 @@ function initGoals(peer, config) {
if (!tangle) break asRoot if (!tangle) break asRoot
const [min, max] = crossGoalWithTangle(goal, tangle) const [min, max] = crossGoalWithTangle(goal, tangle)
if (min > max) break asRoot if (min > max) break asRoot
if (min === 0) return cb(null, 'goal') if (min === 0) return 'goal'
if (min > 0) servesAsTrail = true if (min > 0) servesAsTrail = true
} }
// Check whether this record is a goalful affix of some tangle: // Check whether this record is a goalful affix of some tangle:
const validTangles = const validTangles =
/** @type {Array<[DBTangle, number, number, number]>} */ ([]) /** @type {Array<[DBTangle, number, number, number, GoalType]>} */ ([])
asAffix: for (const tangleID in rec.msg.metadata.tangles) { asAffix: for (const tangleID in rec.msg.metadata.tangles) {
if (!goals.has(tangleID)) continue asAffix if (!goals.has(tangleID)) continue asAffix
const goal = /** @type {GoalImpl} */ (goals.get(tangleID)) const goal = /** @type {GoalImpl} */ (goals.get(tangleID))
@ -219,15 +219,15 @@ function initGoals(peer, config) {
if (min > max) continue asAffix if (min > max) continue asAffix
const recDepth = tangle.getDepth(rec.id) const recDepth = tangle.getDepth(rec.id)
if (recDepth < 0) continue asAffix if (recDepth < 0) continue asAffix
validTangles.push([tangle, min, max, recDepth]) validTangles.push([tangle, min, max, recDepth, goal.type])
} }
// (Loop over once without heavy computations and maybe return early:) // (Loop over once without heavy computations and maybe return early:)
for (const [, min, max, recDepth] of validTangles) { for (const [, min, max, recDepth] of validTangles) {
if (min <= recDepth && recDepth <= max) return cb(null, 'goal') if (min <= recDepth && recDepth <= max) return 'goal'
} }
// At this point we know that the record *cannot* serve as 'goal', // At this point we know that the record *cannot* serve as 'goal',
// so if it serves as trail, that'll do: // so if it serves as trail, that'll do:
if (servesAsTrail) return cb(null, 'trail') if (servesAsTrail) return 'trail'
// Check whether this record is a trail affix of some tangle: // Check whether this record is a trail affix of some tangle:
// (Loop again with heavy computations now that it's inevitable:) // (Loop again with heavy computations now that it's inevitable:)
for (const [tangle, min] of validTangles) { for (const [tangle, min] of validTangles) {
@ -235,27 +235,20 @@ function initGoals(peer, config) {
.topoSort() .topoSort()
.filter((msgID) => tangle.getDepth(msgID) === min) .filter((msgID) => tangle.getDepth(msgID) === min)
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs) const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
if (erasables.has(rec.id)) return cb(null, 'trail') if (erasables.has(rec.id)) return 'trail'
} }
// Check whether this record is a ghost affix of some tangle: // Check whether this record is a ghost affix of some tangle:
if (validTangles.length > 0) { for (const [tangle, , , , goalType] of validTangles) {
const done = /** @type {Multicb<Array<MsgID>>} */ (multicb({ pluck: 1 })) if (goalType === 'record') {
for (const [tangle] of validTangles) { assertRecordPlugin(peer)
peer.db.ghosts.get(tangle.id, done()) if (peer.record.isGhostable(rec.id, tangle.id)) {
} return 'ghost'
done((err, allGhosts) => {
// prettier-ignore
if (err) return cb(new Error('getRecordPurpose() failed to get ghosts', {cause: err}))
for (const ghosts of allGhosts) {
if (ghosts.includes(rec.id)) return cb(null, 'ghost')
} }
cb(null, 'none') }
})
return
} }
cb(null, 'none') return 'none'
} }
/** /**

View File

@ -34,7 +34,7 @@ test('set, getByID, list, listen', async (t) => {
} }
{ {
const purpose = await p(alice.goals.getRecordPurpose)(aliceAccountRoot) const purpose = alice.goals.getRecordPurpose(aliceAccountRoot)
assert.equal(purpose, 'goal', 'rec purpose is "goal"') assert.equal(purpose, 'goal', 'rec purpose is "goal"')
} }
@ -91,17 +91,46 @@ test('getRecordPurpose', async (t) => {
const gottenGoal = alice.goals.get(feedID) const gottenGoal = alice.goals.get(feedID)
assert.strictEqual(gottenGoal.id, feedID, 'gotten goal id is correct') assert.strictEqual(gottenGoal.id, feedID, 'gotten goal id is correct')
const purpose = await p(alice.goals.getRecordPurpose)(post2) const purpose = alice.goals.getRecordPurpose(post2)
assert.equal(purpose, 'goal', 'purpose is "goal"') assert.equal(purpose, 'goal', 'purpose is "goal"')
alice.goals.set(feedID, 'newest-1') alice.goals.set(feedID, 'newest-1')
assert('set goal to newest-1') assert('set goal to newest-1')
const purpose2 = await p(alice.goals.getRecordPurpose)(post2) const purpose2 = alice.goals.getRecordPurpose(post2)
assert.equal(purpose2, 'none', 'purpose2 is "none"') assert.equal(purpose2, 'none', 'purpose2 is "none"')
await p(alice.db.ghosts.add)({ msg: post2.id, tangle: feedID, max: 5 }) await p(alice.close)(true)
const purpose3 = await p(alice.goals.getRecordPurpose)(post2) })
assert.equal(purpose3, 'ghost', 'purpose3 is "ghost"')
test('getRecordPurpose ghost', async (t) => {
const alice = createPeer({ name: 'alice', record: {ghostSpan: 3} })
await alice.db.loaded()
const aliceID = await p(alice.db.account.create)({
domain: 'account',
_nonce: 'alice',
})
await p(alice.record.load)(aliceID)
await p(alice.record.update)('profile', { name: 'alice' })
await p(alice.record.update)('profile', { name: 'Alice' })
await p(alice.record.update)('profile', { name: 'Alicia' })
await p(alice.record.update)('profile', { name: 'ALICIA' })
await p(alice.record.update)('profile', { name: 'ALICIAA' })
const feedID = alice.record.getFeedID('profile')
const tangle = alice.db.getTangle(feedID)
const msgIDs = tangle.topoSort()
assert.equal(msgIDs.length, 6, 'tangle has root+5 messages')
const recs = msgIDs.map(id => alice.db.getRecord(id))
alice.goals.set(feedID, 'record')
assert.equal(alice.goals.getRecordPurpose(recs[1]), 'none')
assert.equal(alice.goals.getRecordPurpose(recs[2]), 'ghost')
assert.equal(alice.goals.getRecordPurpose(recs[3]), 'trail')
assert.equal(alice.goals.getRecordPurpose(recs[4]), 'trail')
assert.equal(alice.goals.getRecordPurpose(recs[5]), 'goal')
await p(alice.close)(true) await p(alice.close)(true)
}) })

View File

@ -18,6 +18,7 @@ function createPeer(opts) {
.use(require('secret-stack/plugins/net')) .use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack')) .use(require('secret-handshake-ext/secret-stack'))
.use(require('ppppp-db')) .use(require('ppppp-db'))
.use(require('ppppp-record'))
.use(require('ssb-box')) .use(require('ssb-box'))
.use(require('../lib')) .use(require('../lib'))
.call(null, { .call(null, {