OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / side-store-channel-priority.test.js

120 lines · javascript ref:
// Two corestores, one connection: whoever replicates FIRST owns the core's channel.
//
// bootMultiDevice replicates several corestores over each connection — the shared store plus, for a
// resynced or pre-cutover DID, that DID's own side store. Corestore keys hypercore channels by DISCOVERY
// KEY on a Protomux that is SHARED per stream (Protomux.from(stream) is cached on the stream), so when two
// of those stores hold the SAME core, the second attach is a duplicate and Protomux answers with null.
//
// The loser does not error, does not conflict, and reports a perfectly healthy local state — it simply
// stays at length 0 forever. That is the worst diagnostic shape there is, and it is indistinguishable from
// "the peer has nothing for us" without knowing to look here.
//
// For a DID whose base lives in a side store, the side store is the AUTHORITATIVE copy (the shared store's
// is the abandoned pre-resync one), so the side store must replicate FIRST. This pins that ordering, and
// pins the negative case so the bug cannot come back by reordering the handler.
//
// It also pins a boundary: a stale core merely ON DISK in the shared store does not STARVE the side
// store, so leftover files are not automatically fatal to the ordering this file is about.
//
// ⚠️ CORRECTED 2026-07-30. This header used to justify that with "corestore does not auto-open cores on
// remote interest". That is FALSE, and the third test never measured it — it asserts only that the SIDE
// store reached length 3, and its on-disk copy is the SAME history as the writer's, so an auto-open
// would have been invisible. corestore 7.11.0 DOES auto-open an on-disk core when a peer announces its
// discovery key (ondiscoverykey -> _attachMaybe -> _openCore, index.js:453-491). The claim was cited as
// evidence that the divergent 4-block copy in stortron's shared store was inert; the conflict watcher
// then caught that copy participating. See test/shared-store-autoopen.test.js, which measures the open
// directly and reproduces the production shape. Scope this test to channel PRIORITY only.
//
// Run: bare test/side-store-channel-priority.test.js
import '../lib/bare-globals.js'
import test from 'brittle'
import fs from 'bare-fs'
import Corestore from 'corestore'

const tmp = (p) => { const d = '/tmp/hpp-chan-' + p + '-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const BLOCKS = [Buffer.from('a'), Buffer.from('b'), Buffer.from('c')]

// A writer store holding one core with 3 blocks — the "incumbent" side of the connection.
async function writer (t) {
  const store = new Corestore(tmp('writer'))
  await store.ready()
  const core = store.get({ name: 'the-base' })
  await core.ready()
  await core.append(BLOCKS)
  t.teardown(async () => { try { await store.close() } catch {} })
  return { store, key: core.key }
}

// Wire one connection replicated by both stores in the given order, and report what the SIDE store got.
async function syncOverOneConn (t, wStore, shared, side, { sideFirst }) {
  const a = wStore.replicate(true)
  const b = sideFirst ? side.replicate(false) : shared.replicate(false)
  if (sideFirst) shared.replicate(b); else side.replicate(b)
  a.pipe(b).pipe(a)
  t.teardown(() => { try { a.destroy(); b.destroy() } catch {} })
  await sleep(1500)
}

test('shared store FIRST starves a side store that holds the same core', async (t) => {
  const w = await writer(t)
  const shared = new Corestore(tmp('shared'))
  const side = new Corestore(tmp('side'))
  await shared.ready(); await side.ready()
  t.teardown(async () => { try { await shared.close(); await side.close() } catch {} })

  const sideCore = side.get({ key: w.key }); await sideCore.ready()
  const sharedCore = shared.get({ key: w.key }); await sharedCore.ready() // the abandoned pre-resync copy

  await syncOverOneConn(t, w.store, shared, side, { sideFirst: false })

  t.is(sideCore.length, 0, 'the SIDE store is starved — stuck at 0, with no error and no conflict')
  t.is(sharedCore.length, 3, 'because the shared store claimed the channel for that discovery key')
})

test('side store FIRST wins the channel — the authoritative copy syncs', async (t) => {
  const w = await writer(t)
  const shared = new Corestore(tmp('shared'))
  const side = new Corestore(tmp('side'))
  await shared.ready(); await side.ready()
  t.teardown(async () => { try { await shared.close(); await side.close() } catch {} })

  const sideCore = side.get({ key: w.key }); await sideCore.ready()
  const sharedCore = shared.get({ key: w.key }); await sharedCore.ready()

  await syncOverOneConn(t, w.store, shared, side, { sideFirst: true })

  t.is(sideCore.length, 3, 'the SIDE store replicated — this is the ordering bootMultiDevice must use')
  t.is(sharedCore.length, 0, 'and the abandoned shared-store copy is the one left behind, which is correct')
})

test('a stale core merely ON DISK in the shared store does not STARVE the side store', async (t) => {
  // The boundary case for channel priority. NOTE what this does and does not show: the side store still
  // syncs, so an unopened on-disk copy does not win the channel here. It does NOT show that the copy
  // stays closed — corestore auto-opens it on remote interest (shared-store-autoopen.test.js), and
  // because this copy is the SAME history as the writer's, an auto-open is invisible to this assertion.
  const w = await writer(t)
  const sharedDir = tmp('shared-ondisk')

  // Put the core on disk in the shared store, then close it so nothing holds it open.
  {
    const s = new Corestore(sharedDir); await s.ready()
    const c = s.get({ key: w.key }); await c.ready()
    const a = w.store.replicate(true); const b = s.replicate(false); a.pipe(b).pipe(a)
    await sleep(1200)
    t.is(c.length, 3, 'precondition: the shared store has the core ON DISK')
    try { a.destroy(); b.destroy() } catch {}
    await sleep(200)
    await s.close()
  }

  const shared = new Corestore(sharedDir); await shared.ready() // reopened, core NOT opened
  const side = new Corestore(tmp('side3')); await side.ready()
  t.teardown(async () => { try { await shared.close(); await side.close() } catch {} })
  const sideCore = side.get({ key: w.key }); await sideCore.ready()

  await syncOverOneConn(t, w.store, shared, side, { sideFirst: false })

  t.is(sideCore.length, 3, 'the side store still replicates — an unopened on-disk copy does not claim the CHANNEL')
})