#!/usr/bin/env node
// Stands up a real JS DHT testnet and runs test_unannounce_live against it.
//
// node run_unannounce_live.js # 8 nodes, static
// NODES=60 GROW=60 node run_unannounce_live.js # 60, then 60 more mid-run
//
// ⛔ GROW IS THE POINT, AND IT WAS ADDED AFTER A MEASUREMENT SAID SO.
//
// A2 claims our commit-phase unannounce reaches fewer nodes than JS's
// during-traversal one. On a STATIC testnet that is false, measured at 8, 60 and
// 120 nodes: the holder set and the closest-K commit set are the same set,
// because a record is stored at the closest K and the commit targets the closest
// K. The traversal hook sent 20 extra UNANNOUNCEs and changed nothing.
//
// The two sets can only differ if the topology MOVES between the announce and
// the unannounce — new nodes arrive closer to the topic than the ones already
// holding it, so the old holders fall out of the top K and a commit-phase
// withdrawal never reaches them. That is not exotic; it is what a DHT does. So
// the harness grows the network in the window between them.
const { spawn } = require('child_process')
const path = require('path')
const createTestnet = require('hyperdht/testnet')
const DHT = require('hyperdht')
const BIN = path.join(__dirname, 'test_unannounce_live')
const NODES = parseInt(process.env.NODES || '8', 10)
const GROW = parseInt(process.env.GROW || '0', 10)
async function main () {
const testnet = await createTestnet(NODES, { teardown: () => {} })
const bootstrap = testnet.bootstrap.map(b => `${b.host}:${b.port}`).join(',')
console.log(`testnet up (${testnet.nodes.length} nodes): ${bootstrap}`)
if (GROW) console.log(`will add ${GROW} more nodes after the announce settles\n`)
else console.log('')
const extra = []
const code = await new Promise((resolve) => {
const p = spawn(BIN, [bootstrap, process.env.PHASE_MS || '8000'], { stdio: ['ignore', 'pipe', 'inherit'] })
let grown = false
p.stdout.on('data', async (chunk) => {
process.stdout.write(chunk)
// Fires between the BEFORE lookup and the unannounce, which is the window
// where new nodes can displace the existing holders in the top K.
if (GROW && !grown && chunk.toString().includes('hold the record; unannouncing')) {
grown = true
for (let i = 0; i < GROW; i++) {
const node = new DHT({ bootstrap: testnet.bootstrap, ephemeral: false })
await node.ready()
extra.push(node)
}
console.log(` [harness] added ${GROW} nodes mid-run`)
}
})
p.on('exit', resolve)
})
for (const n of extra) await n.destroy()
await testnet.destroy()
console.log(`\ntest_unannounce_live exit=${code}`)
process.exit(code)
}
main().catch((err) => { console.error(err); process.exit(1) })