OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room / test / non-indexer-default.test.js

58 lines · javascript ref:
// #133 fix: a regular 2nd device must NOT self-promote to indexer (that made wj62 N=2 = the quorum
// trap). It joins as a plain WRITER, converges, and can still hold the lease + sign — but the indexer
// set stays 1 (the creator = sole indexer, N=1). Only a designated anchor (opts.indexer) grows it.
import '../lib/bare-globals.js'
import test from 'brittle'
import crypto from 'hypercore-crypto'
import fs from 'bare-fs'
import b4a from 'b4a'
import createTestnet from '@hyperswarm/testnet'
import { StateBaseV3 } from '../lib/state-base.js'
import { RepoBase } from '../lib/repo-base.js'
import { makeAttest, makeVerifyAttest, attestDeviceChain, deviceKeetKeyPair, rootIdentityPublicKey } from '../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 DID = 'did:plc:noindex2222222222222'
const POST = 'app.bsky.feed.post'
const tmp = () => { const d = '/tmp/hpp-noidx-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
const sleep = (ms) => new Promise(r => setTimeout(r, ms))

test('a 2nd device joins as a PLAIN WRITER — indexer set stays 1 (N=1, no N=2 trap)', { timeout: 120000 }, async (t) => {
  const testnet = await createTestnet(3)
  const rootPub = await rootIdentityPublicKey(ROOT)
  const devA = await deviceKeetKeyPair(DEVA); const proofA = await attestDeviceChain({ rootMnemonic: ROOT, devicePublicKey: devA.publicKey })
  const devB = await deviceKeetKeyPair(DEVB); const proofB = await attestDeviceChain({ rootMnemonic: ROOT, devicePublicKey: devB.publicKey })
  const members = [{ key: b4a.toString(devA.publicKey, 'hex'), role: 1 }, { key: b4a.toString(devB.publicKey, 'hex'), role: 1 }]
  const roomSeed = crypto.randomBytes(32); const roomKey = crypto.randomBytes(32); const encryptionKey = crypto.randomBytes(32)
  const mk = (dev, proof) => new StateBaseV3(tmp(), roomKey, {
    roomSeed, encryptionKey, swarmOpts: { bootstrap: testnet.bootstrap },
    selfKey: dev.publicKey, attest: makeAttest(dev, proof), verifyAttest: makeVerifyAttest(rootPub),
    getMembers: async () => members
  })
  const A = mk(devA, proofA); await A.ready(); await A.projectMembers(members)
  const B = mk(devB, proofB); await B.ready()
  t.teardown(async () => { try { await A.close(); await B.close() } catch {} ; await testnet.destroy() })

  // B (a role-1 member) writes → admitted as a WRITER + converges (like multi-device).
  const rbB = new RepoBase(B, { did: DID })
  let admitted = false
  for (let i = 0; i < 40 && !admitted; i++) { try { await rbB.putRecord(POST, '3b', { $type: POST, text: 'from B', createdAt: '2026-07-12T00:00:00.000Z' }) } catch {} ; await B.set('__ping__', { i }).catch(() => {}); await sleep(1500); admitted = await B.effectiveWritable().catch(() => false) }
  t.ok(admitted, 'B admitted as a WRITER')
  const rbA = new RepoBase(A, { did: DID })
  let seen = null
  for (let i = 0; i < 30 && !seen; i++) { await A.base.update().catch(() => {}); seen = await rbA.getRecord(POST, '3b'); if (!seen) await sleep(1000) }
  t.ok(seen, 'A converged with B\'s record (writer admission works)')

  // give the promote timer ample chances to (wrongly) fire; assert it does NOT grow the indexer set.
  await sleep(3000); await A.base.update().catch(() => {}); await B.base.update().catch(() => {})
  const idxA = A.base.linearizer.indexers.length
  const idxB = B.base.linearizer.indexers.length
  t.is(idxA, 1, 'indexer set stays 1 (creator A only) — B did NOT self-promote → no N=2 trap')
  t.is(idxB, 1, 'B agrees: exactly one indexer')
  const w0 = A.base.linearizer.indexers[0]
  const soleIdxKey = b4a.toString(w0.core ? w0.core.key : w0.key, 'hex')
  t.is(soleIdxKey, b4a.toString(A.base.key, 'hex'), 'the sole indexer is the bootstrap/creator core (base.key) — NO device self-promoted')
})