OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / relaykernel-circuit-limits-profile.test.js

117 lines · javascript ref:
import test from 'brittle'
import { readFile } from 'fs/promises'
import {
  RELAYKERNEL_CIRCUIT_BYTES_HARD_CAP,
  RELAYKERNEL_CIRCUIT_FRAME_HARD_CAP,
  RELAYKERNEL_CIRCUIT_LIMITS_PROFILE_KIND,
  RELAYKERNEL_CIRCUIT_MAX_PER_PEER_HARD_CAP,
  RELAYKERNEL_CIRCUIT_RECOMMENDED_RATE_CAP_BPS,
  RELAYKERNEL_CIRCUIT_SESSION_HARD_CAP_MS,
  evaluateRelayKernelCircuitLimitsProfile
} from 'p2p-hiverelay/core/protocol/relaykernel-circuit-limits-profile.js'

const VECTOR_URL = new URL(
  '../fixtures/relaykernel-profile/relaykernel-circuit-limits-profile-v1-hard-caps.json',
  import.meta.url
)

async function loadVector () {
  return JSON.parse(await readFile(VECTOR_URL, 'utf8'))
}

function clone (value) {
  return JSON.parse(JSON.stringify(value))
}

test('RelayKernel circuit limits vector pins hard caps and compatibility warnings', async (t) => {
  const vector = await loadVector()
  const profile = evaluateRelayKernelCircuitLimitsProfile(vector.input)

  t.alike(profile, vector.profile, 'circuit limits profile matches fixture')
  t.is(profile.kind, RELAYKERNEL_CIRCUIT_LIMITS_PROFILE_KIND)
  t.is(profile.hardCaps.maxSessionMs, RELAYKERNEL_CIRCUIT_SESSION_HARD_CAP_MS)
  t.is(profile.hardCaps.maxBytes, RELAYKERNEL_CIRCUIT_BYTES_HARD_CAP)
  t.is(profile.hardCaps.maxCircuitsPerPeer, RELAYKERNEL_CIRCUIT_MAX_PER_PEER_HARD_CAP)
  t.is(profile.hardCaps.maxFrameBytes, RELAYKERNEL_CIRCUIT_FRAME_HARD_CAP)
  t.is(profile.hardCaps.recommendedRateBytesPerSecond, RELAYKERNEL_CIRCUIT_RECOMMENDED_RATE_CAP_BPS)
  t.is(profile.verdict.valid, true)
  t.alike(profile.verdict.warnings, [
    'using HiveRelay compatibility channel until rk-circuit is introduced'
  ])
})

test('RelayKernel circuit limits reject upward cap drift', async (t) => {
  const vector = await loadVector()
  const input = clone(vector.input)
  input.limits.maxSessionMs = RELAYKERNEL_CIRCUIT_SESSION_HARD_CAP_MS + 1
  input.limits.maxBytes = RELAYKERNEL_CIRCUIT_BYTES_HARD_CAP + 1
  input.limits.maxCircuitsPerPeer = RELAYKERNEL_CIRCUIT_MAX_PER_PEER_HARD_CAP + 1
  input.limits.maxFrameBytes = RELAYKERNEL_CIRCUIT_FRAME_HARD_CAP + 1
  input.runtime.relayAccountingEnabled = false

  const profile = evaluateRelayKernelCircuitLimitsProfile(input)

  t.is(profile.verdict.valid, false)
  t.ok(profile.verdict.errors.includes('circuit byte cap exceeds 64 MiB hard cap'))
  t.ok(profile.verdict.errors.includes('per-peer circuit cap exceeds 5 hard cap'))
  t.ok(profile.verdict.errors.includes('circuit frame cap exceeds 64 KiB hard cap'))
  t.ok(profile.verdict.errors.includes('circuit session cap exceeds 10 minute hard cap'))
})

test('RelayKernel circuit limits require auth binding and bounded queues', async (t) => {
  const vector = await loadVector()
  const input = clone(vector.input)
  input.runtime.reserveAuthBindsRemoteKey = false
  input.runtime.connectAuthBindsRemoteKey = false
  delete input.limits.maxPendingConnects
  delete input.limits.maxReservePerMinute

  const profile = evaluateRelayKernelCircuitLimitsProfile(input)

  t.is(profile.verdict.valid, false)
  t.ok(profile.verdict.errors.includes('missing circuit security check: reserveAuthBindsRemoteKey'))
  t.ok(profile.verdict.errors.includes('missing circuit security check: connectAuthBindsRemoteKey'))
  t.ok(profile.verdict.errors.includes('reserve attempts are not rate limited'))
  t.ok(profile.verdict.errors.includes('pending connect queue is unbounded'))
})

test('RelayKernel circuit limits model rk-circuit and HiveMesh T1 role admission', (t) => {
  const profile = evaluateRelayKernelCircuitLimitsProfile({
    channels: ['rk-circuit', 't1-circuit'],
    limits: {
      maxSessionMs: RELAYKERNEL_CIRCUIT_SESSION_HARD_CAP_MS,
      maxBytes: RELAYKERNEL_CIRCUIT_BYTES_HARD_CAP,
      maxCircuitsPerPeer: RELAYKERNEL_CIRCUIT_MAX_PER_PEER_HARD_CAP,
      maxFrameBytes: RELAYKERNEL_CIRCUIT_FRAME_HARD_CAP,
      maxPendingConnects: 10,
      maxReservePerMinute: 5,
      rateCapBytesPerSecond: RELAYKERNEL_CIRCUIT_RECOMMENDED_RATE_CAP_BPS
    },
    runtime: {
      reserveAuthBindsRemoteKey: true,
      connectAuthBindsRemoteKey: true,
      endpointOnlyData: true,
      closeOnFrameTooLarge: true,
      closeOnByteCap: true,
      silentDropUnknownCircuit: true,
      relayAccountingEnabled: true,
      relayMaxCircuitDurationMs: RELAYKERNEL_CIRCUIT_SESSION_HARD_CAP_MS,
      circuitRelayCleanupMs: RELAYKERNEL_CIRCUIT_SESSION_HARD_CAP_MS,
      perCircuitRateCapBytesPerSecond: RELAYKERNEL_CIRCUIT_RECOMMENDED_RATE_CAP_BPS
    },
    decisions: [
      { id: 'kernel-client', type: 'open-channel', channel: 'rk-circuit', rolePrefix: 'rkc', expectedAccepted: true },
      { id: 'kernel-relay', type: 'open-channel', channel: 'rk-circuit', rolePrefix: 'rk1', expectedAccepted: true },
      { id: 't1-relay', type: 'open-channel', channel: 't1-circuit', rolePrefix: 'rk1', expectedAccepted: true },
      { id: 't2-vault', type: 'open-channel', channel: 't1-circuit', rolePrefix: 'rk2', expectedAccepted: false },
      { id: 'rate-cap', type: 'byte-rate', bytesPerSecond: RELAYKERNEL_CIRCUIT_RECOMMENDED_RATE_CAP_BPS + 1, expectedAccepted: false }
    ]
  })

  t.is(profile.verdict.valid, true)
  t.alike(profile.verdict.warnings, [])
  t.ok(profile.decisions.every(decision => decision.matchesExpectation))
  t.is(profile.decisions.find(decision => decision.id === 't2-vault').reason, 'E_KEY_TIER')
  t.is(profile.decisions.find(decision => decision.id === 'rate-cap').reason, 'E_CIRCUIT_LIMIT')
})