OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / hivemesh-tombstone-adjudication-profile.test.js

105 lines · javascript ref:
import test from 'brittle'
import { readFile } from 'fs/promises'
import {
  HIVEMESH_TOMBSTONE_ADJUDICATION_PROFILE_KIND,
  evaluateHiveMeshTombstoneAdjudicationProfile
} from 'p2p-hiverelay/core/protocol/hivemesh-tombstone-adjudication-profile.js'

const VECTOR_URL = new URL(
  '../fixtures/relaykernel-profile/hivemesh-tombstone-adjudication-profile-v1-false-tombstone.json',
  import.meta.url
)

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

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

function hex32 (byte) {
  return byte.repeat(32)
}

test('HiveMesh tombstone adjudication vector pins false-tombstone slashing', async (t) => {
  const vector = await loadVector()
  const profile = evaluateHiveMeshTombstoneAdjudicationProfile(vector.input)

  t.alike(profile, vector.profile, 'tombstone adjudication profile matches fixture')
  t.is(profile.kind, HIVEMESH_TOMBSTONE_ADJUDICATION_PROFILE_KIND)
  t.is(profile.tombstoneQuorum.valid, true)
  t.is(profile.tombstoneQuorum.count, 5)
  t.is(profile.contradiction.valid, true)
  t.is(profile.verdict.status, 'false-tombstone')
  t.is(profile.verdict.markRelayExpired, false)
  t.is(profile.verdict.slashWitnesses.length, 5)
})

test('HiveMesh tombstone adjudication confirms non-serving when no same-challenge contradiction exists', async (t) => {
  const vector = await loadVector()
  const input = clone(vector.input)
  input.servingEvidence = []

  const profile = evaluateHiveMeshTombstoneAdjudicationProfile(input)

  t.is(profile.tombstoneQuorum.valid, true)
  t.is(profile.contradiction.valid, false)
  t.is(profile.contradiction.reason, 'insufficient-serving-contradiction')
  t.is(profile.verdict.status, 'non-serving-confirmed')
  t.is(profile.verdict.markRelayExpired, true)
  t.alike(profile.verdict.slashWitnesses, [])
})

test('HiveMesh tombstone adjudication does not treat no-answer as non-serving without retries and grace', async (t) => {
  const vector = await loadVector()
  const input = clone(vector.input)
  input.servingEvidence = []
  input.tombstones = input.tombstones.slice(0, 5).map((entry, index) => {
    if (index === 0) return { ...entry, attempts: 1 }
    if (index === 1) return { ...entry, at: input.intent.retainUntil + 29_999 }
    return entry
  })

  const profile = evaluateHiveMeshTombstoneAdjudicationProfile(input)

  t.is(profile.tombstoneQuorum.valid, false)
  t.is(profile.tombstoneQuorum.count, 0)
  t.is(profile.verdict.status, 'insufficient-tombstone-quorum')
  t.ok(profile.tombstoneQuorum.rejected.find(row => row.reason === 'retry policy not met'))
  t.ok(profile.tombstoneQuorum.rejected.find(row => row.reason === 'observation before expiry grace'))
})

test('HiveMesh tombstone adjudication requires tombstones to bind a known non-serving proof', async (t) => {
  const vector = await loadVector()
  const input = clone(vector.input)
  input.nonServingProofs = []
  input.servingEvidence = []
  input.tombstones = input.tombstones.slice(0, 5)

  const profile = evaluateHiveMeshTombstoneAdjudicationProfile(input)

  t.is(profile.tombstoneQuorum.valid, false)
  t.is(profile.tombstoneQuorum.reason, 'no-accepted-tombstones')
  t.is(profile.verdict.status, 'insufficient-tombstone-quorum')
  t.is(profile.tombstoneQuorum.rejected.length, 5)
  t.ok(profile.tombstoneQuorum.rejected.every(row => row.reason === 'matching non-serving proof required'))
})

test('HiveMesh tombstone adjudication requires contradiction evidence to bind the tombstone challenge', async (t) => {
  const vector = await loadVector()
  const input = clone(vector.input)
  input.servingEvidence = input.servingEvidence.map(entry => ({
    ...entry,
    challengeNonce: hex32('33')
  }))

  const profile = evaluateHiveMeshTombstoneAdjudicationProfile(input)

  t.is(profile.tombstoneQuorum.valid, true)
  t.is(profile.contradiction.valid, false)
  t.is(profile.contradiction.rejected[0].reason, 'challengeNonce mismatch')
  t.is(profile.verdict.status, 'non-serving-confirmed')
  t.alike(profile.verdict.slashWitnesses, [])
})