OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-identity / test / apply-determinism.test.js

103 lines · javascript ref:
// Autobase requires apply to be DETERMINISTIC: it is replayed on every boot
// and every reorg, and each peer runs it independently to build its own view.
// If apply stamps non-deterministic values, two peers replaying identical ops
// write DIFFERENT view bytes and their views can never agree.
//
// hyper-rooms (lib/room.js) — the reference implementation on this same
// substrate — puts the timestamp in the OP (:220,260,273) and has apply read
// `op.timestamp || Date.now()` (:418). This module must do the same.
//
// CONTROL first: an op that stores only op-provided fields must replay
// byte-identically, proving the harness itself is deterministic.

import test from 'brittle'
import Corestore from 'corestore'
import Hyperbee from 'hyperbee'
import path from '#node:path'
import os from '#node:os'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import { createApplyFunction } from '../lib/auth/identity-apply.js'
import { encodeOp, OpTypes } from '../lib/auth/identity-operations.js'

const stubHost = { addWriter: async () => {}, removeWriter: async () => {} }
const node = (op) => ({ value: encodeOp(op), from: { key: b4a.alloc(32) } })
const sleep = (ms) => new Promise(r => setTimeout(r, ms))

// Run the real apply over a batch into a fresh in-memory view, return the
// stored value at `key`.
async function applyAndRead (batch, key) {
  const store = new Corestore(
    path.join(os.tmpdir(), 'hyper-identity-determinism-' + crypto.randomBytes(8).toString('hex'))
  )
  await store.ready()
  const bee = new Hyperbee(store.get({ name: 'identity-view' }), {
    keyEncoding: 'utf-8', valueEncoding: 'json'
  })
  await bee.ready()

  const apply = createApplyFunction({})
  await apply(batch, bee, stubHost)

  const entry = await bee.get(key)
  const value = entry ? entry.value : null
  await bee.close()
  await store.close()
  return value
}

test('CONTROL: an op storing only op-provided fields replays byte-identically', async (t) => {
  const identity = {
    identityId: 'ident-control',
    method: 'web-identity',
    publicKey: 'ff'.repeat(32),
    userId: 'user-control',
    createdAt: 1700000000000
  }
  const batch = [node({ type: OpTypes.REGISTER_IDENTITY, identity })]

  const a = await applyAndRead(batch, 'identity:ident-control')
  await sleep(25)
  const b = await applyAndRead(batch, 'identity:ident-control')

  t.ok(a && b, 'both replays stored the record')
  t.alike(a, b, 'control replays byte-identical — harness is deterministic')
})

test('ADD_WRITER replays deterministically', async (t) => {
  const writerKey = 'ab'.repeat(32)
  const batch = [node({
    type: OpTypes.ADD_WRITER,
    writerKey,
    role: 'writer',
    addedBy: 'test',
    timestamp: 1700000000001
  })]

  const a = await applyAndRead(batch, 'writer:' + writerKey)
  await sleep(25)
  const b = await applyAndRead(batch, 'writer:' + writerKey)

  t.ok(a && b, 'both replays stored the writer record')
  t.alike(a, b, 'identical ADD_WRITER op produces identical view bytes on replay')
  t.is(a.addedAt, 1700000000001, 'addedAt comes from the OP, not from apply')
})

test('REGISTER_MIRROR_WRITERS replays deterministically', async (t) => {
  const devicePubkey = 'cd'.repeat(32)
  const batch = [node({
    type: OpTypes.REGISTER_MIRROR_WRITERS,
    devicePubkey,
    keys: { orgs: 'ef'.repeat(32) },
    timestamp: 1700000000002
  })]

  const a = await applyAndRead(batch, 'mirror-writers:' + devicePubkey)
  await sleep(25)
  const b = await applyAndRead(batch, 'mirror-writers:' + devicePubkey)

  t.ok(a && b, 'both replays stored the announcement')
  t.alike(a, b, 'identical REGISTER_MIRROR_WRITERS op produces identical view bytes')
  t.is(a.recordedAt, 1700000000002, 'recordedAt comes from the OP, not from apply')
})