OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / resync-creator-fork.repro.mjs

61 lines · javascript ref:
// REPRO: resync-did's "fresh corestore → clean local writer" premise is FALSE for the CREATOR.
//
// The shipped resync suite (resync-fresh-corestore.test.js) only exercises the HIGHER-key NON-creator
// as the rejoiner (see its line 49 comment). In prod the poisoned device is the LOWEST-key = CREATOR
// (stortron/wj62). This proves the gap: a creator opens its base with keyPair=_gsKeyPair (deterministic,
// roomSeed-derived — deriveGsKeyPair), so a fresh corestore RE-DERIVES THE SAME local writer key. The
// writer is NOT shed → re-adopting it empty forks the genesis core (the df0c052a conflict seen live).
//
// EXPECTED (documents the bug): CREATOR freshLocal === origLocal (NOT shed); NON-creator differs (shed).
import '../lib/bare-globals.js'
import test from 'brittle'
import crypto from 'hypercore-crypto'
import fs from 'bare-fs'
import b4a from 'b4a'
import { StateBaseV3 } from 'atproto-room/lib/state-base.js'
import { makeAttest, makeVerifyAttest, attestDeviceChain, deviceKeetKeyPair, rootIdentityPublicKey } from 'atproto-room/lib/membership.js'

const ROOT = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
const DEVA = 'legal winner thank year wave sausage worth useful legal winner thank yellow'
const DEVB = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
const tmp = () => { const d = '/tmp/hpp-creatorfork-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }

test('resync fresh-corestore SHEDS the writer for a NON-creator but NOT for the CREATOR (the prod gap)', { timeout: 120000 }, async (t) => {
  const rootPub = await rootIdentityPublicKey(ROOT)
  const d1 = await deviceKeetKeyPair(DEVA); const p1 = await attestDeviceChain({ rootMnemonic: ROOT, devicePublicKey: d1.publicKey })
  const d2 = await deviceKeetKeyPair(DEVB); const p2 = await attestDeviceChain({ rootMnemonic: ROOT, devicePublicKey: d2.publicKey })
  // lowest key = creator; higher key = non-creator
  let lo = { dev: d1, proof: p1 }, hi = { dev: d2, proof: p2 }
  if (b4a.compare(lo.dev.publicKey, hi.dev.publicKey) > 0) { const s = lo; lo = hi; hi = s }
  const members = [
    { key: b4a.toString(lo.dev.publicKey, 'hex'), role: 1 },
    { key: b4a.toString(hi.dev.publicKey, 'hex'), role: 1 }
  ]
  // ONE room identity, reused across the "resync" reopen (roomSeed is the deterministic genesis input).
  const roomSeed = crypto.randomBytes(32); const roomKey = crypto.randomBytes(32); const encryptionKey = crypto.randomBytes(32)
  const mk = (who, dir) => new StateBaseV3(dir, roomKey, {
    roomSeed, encryptionKey, network: false,
    selfKey: who.dev.publicKey, attest: makeAttest(who.dev, who.proof), verifyAttest: makeVerifyAttest(rootPub),
    getMembers: async () => members
  })

  const localKeyAcross = async (who) => {
    const s1 = mk(who, tmp()); await s1.ready()
    const amCreator = await s1._amCreator()
    const orig = b4a.toString(s1.base.local.key, 'hex')
    await s1.close()
    const s2 = mk(who, tmp()); await s2.ready() // FRESH corestore = the resync side store
    const fresh = b4a.toString(s2.base.local.key, 'hex')
    await s2.close()
    return { amCreator, orig, fresh }
  }

  const c = await localKeyAcross(lo)
  t.ok(c.amCreator, 'sanity: the lowest-key device elects as CREATOR')
  t.is(c.fresh, c.orig, 'BUG: CREATOR fresh-corestore RE-DERIVES the SAME local writer (deterministic _gsKeyPair) — NOT shed')

  const n = await localKeyAcross(hi)
  t.absent(n.amCreator, 'sanity: the higher-key device is a NON-creator')
  t.not(n.fresh, n.orig, 'CONTROL: NON-creator fresh-corestore yields a DIFFERENT (clean) local writer — shed works (what the shipped suite proves)')
})