OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-did-peer / test / router.test.mjs

91 lines · javascript ref:
// DidPeerRouter regression — multi-DID reading over ONE shared swarm. Two holders serve real signed
// repos (RepoManager + FirehoseSequencer) on an in-process testnet; a reader must learn BOTH.
//   CONTROL: per-DidPeer attach() collides on the single announce channel -> only the FIRST-added DID
//            reads; the second never learns its cores (the bug).
//   FIX:     DidPeerRouter dispatches each announce by DID -> BOTH read.
// Run: node test/router.test.mjs
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import createTestnet from '@hyperswarm/testnet'
import Hyperswarm from 'hyperswarm'
import { RepoStore } from 'atproto-store/lib/repo-store.js'
import { RepoManager } from 'atproto-store/lib/repo-manager.js'
import { FirehoseSequencer } from 'atproto-store/lib/sequencer.js'
import { openCorestore } from 'atproto-store/lib/corestore.js'
import { createAtprotoIdentityFromKey } from 'atproto-identity/lib/atproto-identity.js'
import { discoveryTopic } from 'atproto-identity/lib/discovery.js'
import { DidPeer, DidPeerRouter } from '../index.js'

const assert = (c, m) => { if (!c) { console.error('FAIL:', m); cleanupSync(); process.exit(1) } console.log('  ok -', m) }
const iso = '2026-07-01T00:00:00.000Z'
const dirs = []
const toClose = []
function newDir (p) { const d = mkdtempSync(join(tmpdir(), p)); dirs.push(d); return d }
function cleanupSync () { for (const d of dirs) { try { rmSync(d, { recursive: true, force: true }) } catch {} } }

const idA = createAtprotoIdentityFromKey(new Uint8Array(32).fill(9), { handle: 'a.test', pdsUrl: 'https://pds.local' })
const idB = createAtprotoIdentityFromKey(new Uint8Array(32).fill(11), { handle: 'b.test', pdsUrl: 'https://pds.local' })

const testnet = await createTestnet(3)
const bootstrap = testnet.bootstrap

// A holder authors a signed repo + sequences its commit, then serves the cores on its own swarm.
async function makeHolder (id, text) {
  const store = openCorestore(newDir('holder-'))
  const repoStore = await new RepoStore(store, { name: `atproto-repo:${id.did}` }).ready()
  let micros = 1_700_000_000_000_000n
  const mgr = await new RepoManager(repoStore, { did: id.did, privateKey: id.privateKey, clock: () => (micros += 1_000_000n) }).init()
  const write = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '1', record: { $type: 'app.bsky.feed.post', text, createdAt: iso } })
  const seq = await new FirehoseSequencer(store.get({ name: 'firehose' })).ready()
  await seq.sequenceCommit(write, { time: iso })
  const peer = new DidPeer(store, { did: id.did, verifyKey: id.publicKey })
  await peer.provide({ repoKey: repoStore.core.key, firehoseKey: seq.core.key })
  const swarm = new Hyperswarm({ bootstrap })
  swarm.on('connection', (c) => peer.attach(c))
  await swarm.join(discoveryTopic(id.did), { server: true, client: true }).flushed()
  toClose.push(async () => { try { await swarm.destroy() } catch {}; try { await peer.close() } catch {}; try { await store.close() } catch {} })
  return peer
}

const holderA = await makeHolder(idA, 'hello from A')
const holderB = await makeHolder(idB, 'hello from B')
console.log('holders serving:', idA.did.slice(0, 20), '+', idB.did.slice(0, 20))

// --- CONTROL: per-peer attach() — only the FIRST-added DID reads ---
{
  const store = openCorestore(newDir('ctrl-'))
  const swarm = new Hyperswarm({ bootstrap })
  const cA = new DidPeer(store, { did: idA.did, verifyKey: idA.publicKey })
  const cB = new DidPeer(store, { did: idB.did, verifyKey: idB.publicKey })
  swarm.on('connection', (c) => cA.attach(c)) // cA registered FIRST -> grabs the channel on every conn
  swarm.on('connection', (c) => cB.attach(c))
  await swarm.join(discoveryTopic(idA.did), { server: true, client: true }).flushed()
  await swarm.join(discoveryTopic(idB.did), { server: true, client: true }).flushed()
  const [chA, chB] = await Promise.all([cA.ready({ timeout: 15000 }), cB.ready({ timeout: 15000 })])
  assert(!!chA, 'CONTROL: the first-added DID (A) reads')
  assert(!chB, 'CONTROL: per-peer attach() — the SECOND DID (B) never reads (reproduces the bug)')
  await swarm.destroy(); await cA.close(); await cB.close(); await store.close()
}

// --- FIX: DidPeerRouter dispatches by DID — BOTH read ---
{
  const store = openCorestore(newDir('router-'))
  const swarm = new Hyperswarm({ bootstrap })
  const router = new DidPeerRouter(store)
  swarm.on('connection', (c) => router.onconnection(c))
  const pA = router.add(idA.did, { verifyKey: idA.publicKey })
  const pB = router.add(idB.did, { verifyKey: idB.publicKey })
  await swarm.join(discoveryTopic(idA.did), { server: true, client: true }).flushed()
  await swarm.join(discoveryTopic(idB.did), { server: true, client: true }).flushed()
  const [hA, hB] = await Promise.all([pA.ready({ timeout: 20000 }), pB.ready({ timeout: 20000 })])
  assert(!!hA, 'ROUTER: DID A reaches a verified head')
  assert(!!hB, 'ROUTER: DID B reaches a verified head (both members read — the fix)')
  await swarm.destroy(); await router.close(); await store.close()
}

console.log('\nROUTER OK: per-peer attach reads only the first DID; DidPeerRouter reads BOTH')
for (const close of toClose) await close()
await testnet.destroy()
cleanupSync()