OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / seeding-registry-hardening.test.js

163 lines · javascript ref:
import test from 'brittle'
import { randomBytes } from 'crypto'
import { SeedingRegistry } from 'p2p-hiverelay/core/registry/index.js'

function mockStore () {
  return {
    get () {
      return {
        key: randomBytes(32),
        length: 0,
        async ready () {},
        replicate () {},
        on () {},
        async close () {},
        async get () { return null }
      }
    }
  }
}

function mockSwarm () {
  return {
    keyPair: { publicKey: randomBytes(32) }
  }
}

function registryForStop (events, discoveryHandle) {
  const registry = new SeedingRegistry({}, {
    removeListener (event) { events.push(`swarm-remove-${event}`) }
  })
  registry.running = true
  registry._discoveryHandle = discoveryHandle
  registry._onSwarmConnection = () => {}
  registry._onLocalAppend = () => {}
  registry.localLog = {
    removeListener (event) { events.push(`local-remove-${event}`) },
    async close () { events.push('local-close') }
  }
  return registry
}

test('SeedingRegistry - rejects meta announce when declared peer key mismatches transport key', (t) => {
  const registry = new SeedingRegistry(mockStore(), mockSwarm())
  let called = 0
  registry._registerPeerLog = async () => { called++ }

  const transportKey = randomBytes(32)
  registry._onMetaMessage({}, { publicKey: transportKey }, {
    type: 0,
    logKey: 'a'.repeat(64),
    peerPubkey: 'b'.repeat(64)
  })

  t.is(called, 0, 'registry ignores mismatched peer identity claim')
})

test('SeedingRegistry - rejects forged seed-accept relay pubkey attribution', (t) => {
  const registry = new SeedingRegistry(mockStore(), mockSwarm())
  const appKey = 'a'.repeat(64)

  registry._applyEntry({
    type: 'seed-accept',
    timestamp: Date.now(),
    appKey,
    relayPubkey: 'b'.repeat(64),
    region: 'na'
  }, {
    logId: 'deadbeef',
    peerPubkey: 'c'.repeat(64)
  })

  t.is(registry._acceptances.get(appKey), undefined, 'mismatched relay identity not indexed')
})

test('SeedingRegistry - accepts seed-accept when relay identity matches log peer', (t) => {
  const registry = new SeedingRegistry(mockStore(), mockSwarm())
  const appKey = 'a'.repeat(64)
  const relayPubkey = 'd'.repeat(64)

  registry._applyEntry({
    type: 'seed-accept',
    timestamp: Date.now(),
    appKey,
    relayPubkey,
    region: 'na'
  }, {
    logId: 'feedface',
    peerPubkey: relayPubkey
  })

  const acceptances = registry._acceptances.get(appKey)
  t.ok(Array.isArray(acceptances), 'acceptance list created')
  t.is(acceptances.length, 1, 'matching acceptance indexed')
  t.is(acceptances[0].relayPubkey, relayPubkey, 'relay pubkey preserved')
})

test('SeedingRegistry - enforces max peer log cap', async (t) => {
  const registry = new SeedingRegistry(mockStore(), mockSwarm(), { maxPeerLogs: 1 })
  registry.localLog = { key: randomBytes(32) }
  registry._peerLogMeta.set('f'.repeat(64), {
    log: { replicate () {} },
    onAppend: null,
    peerPubkey: '1'.repeat(64)
  })

  await registry._registerPeerLog('a'.repeat(64), '2'.repeat(64), {})
  t.is(registry._peerLogMeta.size, 1, 'new peer log rejected once cap is reached')
})

test('SeedingRegistry - stop awaits its discovery handle before closing logs', async (t) => {
  const events = []
  let settleDiscovery
  const discoverySettled = new Promise(resolve => { settleDiscovery = resolve })
  const registry = registryForStop(events, {
    async destroy () {
      events.push('discovery-destroy-start')
      await discoverySettled
      events.push('discovery-destroy-done')
    }
  })

  const stopping = registry.stop()
  await new Promise(resolve => setImmediate(resolve))
  t.alike(events, [
    'swarm-remove-connection',
    'local-remove-append',
    'discovery-destroy-start'
  ])
  t.ok(registry.localLog, 'registry log remains open while discovery retirement is pending')

  settleDiscovery()
  await stopping
  t.alike(events.slice(-3), ['discovery-destroy-start', 'discovery-destroy-done', 'local-close'])
  t.is(registry._discoveryHandle, null)
  t.is(registry.localLog, null)
  t.is(registry._stopping, false)
})

test('SeedingRegistry - rejected discovery retirement retains handle and logs for retry', async (t) => {
  const events = []
  let rejectDestroy = true
  const handle = {
    async destroy () {
      events.push('discovery-destroy')
      if (rejectDestroy) throw new Error('injected registry discovery failure')
    }
  }
  const registry = registryForStop(events, handle)

  await t.exception(registry.stop(), /injected registry discovery failure/)
  t.is(registry._discoveryHandle, handle)
  t.ok(registry.localLog, 'registry log remains owned after failed discovery retirement')
  t.is(registry._stopping, true)
  t.absent(events.includes('local-close'))

  rejectDestroy = false
  await registry.stop()
  t.is(registry._discoveryHandle, null)
  t.is(registry.localLog, null)
  t.is(registry._stopping, false)
  t.ok(events.includes('local-close'))
})