OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / multi-device.test.js

65 lines · javascript ref:
// The goal proof: TWO attested devices of ONE DID share one repo. Device B (a second attested
// device) writes a repo intent; device A admits it via the attestation-admission path; the
// linearized view converges so BOTH devices see the record. Real StateBaseV3 replication over a
// local testnet. This is multi-device — N equal devices, one DID.

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 'atproto-room/lib/state-base.js'
import { RepoBase } from 'atproto-room/lib/repo-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 DID = 'did:plc:swarm2222222222222222222'
const POST = 'app.bsky.feed.post'

function tmp () { const d = '/tmp/hpp-md-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
const sleep = (ms) => new Promise(r => setTimeout(r, ms))

test('two attested devices of one DID share one repo', 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) // seed the __members__/__devices__ roster
  const B = mk(devB, proofB); await B.ready()
  t.teardown(async () => { try { await A.close(); await B.close() } catch {} ; await testnet.destroy() })

  // B (second device) writes a repo intent — admitted by A via attestation, then it linearizes.
  const rbB = new RepoBase(B, { did: DID })
  const rec = { $type: POST, text: 'posted from device B', createdAt: '2026-07-04T00:00:00.000Z' }

  let admitted = false
  for (let i = 0; i < 40 && !admitted; i++) {
    try { await rbB.putRecord(POST, '3fromB', rec) } catch {}
    await B.set('__ping__', { i }).catch(() => {}) // nudge replication/admission
    await sleep(1500)
    admitted = await B.effectiveWritable().catch(() => false)
  }
  t.ok(admitted, 'device B was admitted as a writer via attestation')

  // convergence: A (the other device) sees B's record
  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, '3fromB'); if (!seen) await sleep(1000) }
  t.ok(seen && seen.text === rec.text, "device A sees device B's record (one shared repo)")
})