Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/core-manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,11 @@ async function purgeCore(core) {
const tx = privateCore.storage.write()
tx.deleteTreeNodeRange(0, -1)
tx.deleteBlockRange(0, -1)
// Also delete the head record (fork, length, root hash): a header claiming
// length > 0 with no merkle roots in storage puts the core in hypercore's
// repair mode on next open, in which it never sends a Synchronize and so
// never syncs again
tx.deleteHead()
// @ts-ignore Private methods on storage
privateCore.bitfield.clear(tx)
await tx.flush()
Expand Down
46 changes: 46 additions & 0 deletions test/core-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { Transform } from 'streamx'
import { waitForCores } from './helpers/core-manager.js'
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { createCore } from './helpers/create-core.js'
import { temporaryDirectory } from 'tempy'
import fsPromises from 'node:fs/promises'
/** @import { Namespace } from '../src/types.js' */

test('project creator auth core has project key', async function (t) {
Expand Down Expand Up @@ -555,3 +557,47 @@ function latencyStream(delay = 0) {
},
})
}

test('deleteOthersData leaves purged cores usable after re-opening storage', async function (t) {
const projectKey = randomBytes(32)
const cm1 = createCoreManager(t, { projectKey })

// cm2 gets stable identity, storage and db so it can be re-opened, as
// happens when a device re-joins a project after leaving
const rootKey = randomBytes(16)
const db = drizzle(new Sqlite(':memory:'))
const storage = temporaryDirectory()
t.after(() => fsPromises.rm(storage, { recursive: true, force: true }))

const cm2 = createCoreManager(t, { projectKey, rootKey, db, storage }, false)

const writer = cm1.getWriterCore('data')
await writer.core.append(['a', 'b', 'c'])

cm2.addCore(writer.key, 'data')
const rep1 = await replicate(cm1, cm2)
const core2 = cm2.getCoreByKey(writer.key)
assert(core2, 'core was added')
await core2.download({ start: 0, end: 3 }).done()
await rep1.destroy()

await cm2.deleteOthersData('data')
await cm2.close()

const cm2reopened = createCoreManager(t, { projectKey, rootKey, db, storage })
cm2reopened.addCore(writer.key, 'data')
const core2reopened = cm2reopened.getCoreByKey(writer.key)
assert(core2reopened, 'core was re-added')
await core2reopened.ready()

const rep2 = await replicate(cm1, cm2reopened)
t.after(() => rep2.destroy())

const block = await Promise.race([
core2reopened.get(0),
delay(5000).then(() => {
throw new Error('timed out re-syncing the purged core')
}),
])
assert.deepEqual(block, Buffer.from('a'), 're-downloaded purged block')
})
Loading