OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / run_unannounce_live.js

65 lines · javascript ref:
#!/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) })