OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / device-invite-fix-proof.test.js

159 lines · javascript ref:
// Proof-of-fix test for the device-invite ↔ IdentityRegistry contract.
//
// Hypothesis (already empirically confirmed by device-invite-writer-mismatch.test.js):
// the source pre-computes the target's autobase writer key from newDeviceKp;
// for that prediction to hold, the target's IdentityRegistry must open
// the autobase with `keyPair: newDeviceKp` AND `bootstrap: source's autobase root`.
//
// This test models the canonical end-to-end flow using only autobase + corestore +
// Hypercore.key — no pear-git-v3 wrappers, no daemon, no Hyperswarm. If this passes,
// the fix is mechanically sound; we then apply it to lib/pear-git.js's
// IdentityRegistry construction (mirrors only).
//
// Flow modelled:
//   1. PRIMARY creates Autobase with keyPair=primaryKp. autobase.key = root.
//   2. SOURCE generates newDeviceKp for a NEW device (mirror). Computes
//      pre = Hypercore.key({signers:[{publicKey: newDeviceKp.publicKey}]}).
//   3. PRIMARY (= source's daemon) appends an addWriter op for `pre`.
//   4. MIRROR opens its own corestore + Autobase with bootstrap=primary.base.key
//      AND keyPair=newDeviceKp. mirror.base.local.key MUST equal `pre`.
//   5. Replicate corestores. Mirror should become writable. Mirror appends.
//   6. Primary applies mirror's op into its view. signedLength advances.

import test from 'brittle'
import Autobase from 'autobase'
import Corestore from 'corestore'
import Hypercore from 'hypercore'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import os from '#node:os'
import path from '#node:path'
import fs from '#node:fs'
import { replicateAndSync } from 'autobase-test-helpers'

const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'dvi-fix-proof-'))
let n = 0
const tmp = () => path.join(TMP, 'store-' + (n++))

async function apply (nodes, view, host) {
  for (const node of nodes) {
    const v = node.value
    if (v && v.type === 'add-writer') {
      await host.addWriter(b4a.from(v.writerKey, 'hex'), { indexer: false })
    } else {
      await view.append(v)
    }
  }
}

const baseOpts = {
  apply,
  open: (s) => s.get('view', { valueEncoding: 'json' }),
  valueEncoding: 'json',
  optimistic: true,
  ackInterval: 1000,
  ackThreshold: 4
}

test('source pre-compute + mirror keyPair = mirror becomes writable cleanly', async (t) => {
  // ── PRIMARY (source) ─────────────────────────────────────────
  const primaryKp = crypto.keyPair()  // = hyperident master keypair on source
  const primaryStore = new Corestore(tmp())
  await primaryStore.ready()
  const primary = new Autobase(primaryStore, null, { ...baseOpts, keyPair: primaryKp })
  await primary.ready()

  await primary.append({ msg: 'primary boot' })
  await primary.update()

  // ── DEVICE INVITE (source side) ──────────────────────────────
  // Identical to lib/device-linker.js createInvite:
  const newDeviceKp = crypto.keyPair()
  const predictedWriterKey = Hypercore.key({ version: 1, signers: [{ publicKey: newDeviceKp.publicKey }] })

  // Source addWriters predicted key — identical to lib/handlers/devices.js:102.
  await primary.append({ type: 'add-writer', writerKey: b4a.toString(predictedWriterKey, 'hex') })
  await primary.update()

  // ── MIRROR opens with bootstrap=primary.key + keyPair=newDeviceKp ──
  const mirrorStore = new Corestore(tmp())
  await mirrorStore.ready()
  const mirror = new Autobase(mirrorStore, primary.key, { ...baseOpts, keyPair: newDeviceKp })
  await mirror.ready()

  // PRIMARY pre-computed predictedWriterKey based on newDeviceKp.publicKey.
  // MIRROR's actual local.key must equal predictedWriterKey for the addWriter
  // to register the right writer.
  console.log('  predicted (source addWritered): ', b4a.toString(predictedWriterKey, 'hex').slice(0, 16) + '…')
  console.log('  mirror.local.key (actual):      ', b4a.toString(mirror.local.key, 'hex').slice(0, 16) + '…')
  t.ok(b4a.equals(mirror.local.key, predictedWriterKey),
    'mirror.local.key === predictedWriterKey (DeviceLinker prediction holds when target uses keyPair=newDeviceKp)')

  // ── Replicate primary <-> mirror via in-process stream pipe ─
  const sa = primaryStore.replicate(true)
  const sb = mirrorStore.replicate(false)
  sa.on('error', () => {}); sb.on('error', () => {})
  sa.pipe(sb).pipe(sa)

  await replicateAndSync([primary, mirror])

  t.ok(mirror.writable, 'mirror.writable === true after sync (addWriter was for the right key)')

  // ── Mirror appends, primary sees it ──────────────────────────
  await mirror.append({ msg: 'mirror boot, can write' })
  await mirror.update()
  await replicateAndSync([primary, mirror])

  let seenMirrorMsg = false
  for (let i = 0; i < primary.view.length; i++) {
    const v = await primary.view.get(i)
    if (v && v.msg === 'mirror boot, can write') { seenMirrorMsg = true; break }
  }
  t.ok(seenMirrorMsg, 'primary.view contains mirror\'s op (cross-machine write linearized)')

  console.log('  primary.signedLength:', primary.signedLength)
  console.log('  mirror.signedLength: ', mirror.signedLength)
  t.ok(primary.signedLength > 0 && mirror.signedLength > 0, 'both peers have advanced signedLength')

  await primary.close(); await primaryStore.close()
  await mirror.close(); await mirrorStore.close()
})

test('CONTROL: same setup but mirror opened WITHOUT keyPair → mirror is NOT writable (reproduces today\'s prod bug)', async (t) => {
  const primaryKp = crypto.keyPair()
  const primaryStore = new Corestore(tmp())
  await primaryStore.ready()
  const primary = new Autobase(primaryStore, null, { ...baseOpts, keyPair: primaryKp })
  await primary.ready()
  await primary.append({ msg: 'primary boot' })
  await primary.update()

  const newDeviceKp = crypto.keyPair()
  const predictedWriterKey = Hypercore.key({ version: 1, signers: [{ publicKey: newDeviceKp.publicKey }] })
  await primary.append({ type: 'add-writer', writerKey: b4a.toString(predictedWriterKey, 'hex') })
  await primary.update()

  // Mirror opens WITHOUT keyPair — exactly today's post-revert IdentityRegistry.
  const mirrorStore = new Corestore(tmp())
  await mirrorStore.ready()
  const mirror = new Autobase(mirrorStore, primary.key, { ...baseOpts /* no keyPair */ })
  await mirror.ready()

  const sa = primaryStore.replicate(true)
  const sb = mirrorStore.replicate(false)
  sa.on('error', () => {}); sb.on('error', () => {})
  sa.pipe(sb).pipe(sa)
  await replicateAndSync([primary, mirror])

  console.log('  mirror.local.key:                ', b4a.toString(mirror.local.key, 'hex').slice(0, 16) + '…')
  console.log('  predicted (source addWritered):  ', b4a.toString(predictedWriterKey, 'hex').slice(0, 16) + '…')

  t.absent(b4a.equals(mirror.local.key, predictedWriterKey),
    'mirror.local.key !== predictedWriterKey (today\'s bug: source addWritered the wrong key)')
  t.absent(mirror.writable,
    'mirror NOT writable — exactly the prod symptom: stortron returns "Not writable" on mutating RPCs')

  await primary.close(); await primaryStore.close()
  await mirror.close(); await mirrorStore.close()
})