Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
262 additions
and
206 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
const crypto = require('hypercore-crypto') | ||
const random = require('math-random-seed') | ||
const { timeout } = require('nonsynchronous') | ||
|
||
const Hyperswarm = require('..') | ||
const { test, destroyAll } = require('./helpers') | ||
|
||
const BACKOFFS = [ | ||
100, | ||
200, | ||
300 | ||
] | ||
|
||
test('chaos - recovers after random disconnections (takes ~60s)', async (bootstrap, t) => { | ||
const SEED = 'hyperswarm v3' | ||
const NUM_SWARMS = 10 | ||
const NUM_TOPICS = 15 | ||
const NUM_FORCE_DISCONNECTS = 30 | ||
|
||
const STARTUP_DURATION = 1000 * 5 | ||
const TEST_DURATION = 1000 * 45 | ||
const CHAOS_DURATION = 1000 * 10 | ||
|
||
const swarms = [] | ||
const topics = [] | ||
const connections = [] | ||
const peersBySwarm = new Map() | ||
const rand = random(SEED) | ||
|
||
for (let i = 0; i < NUM_SWARMS; i++) { | ||
const swarm = new Hyperswarm({ bootstrap, backoffs: BACKOFFS, jitter: 0 }) | ||
swarms.push(swarm) | ||
peersBySwarm.set(swarm, new Set()) | ||
swarm.on('connection', conn => { | ||
connections.push(conn) | ||
|
||
conn.on('error', noop) | ||
conn.on('close', () => { | ||
clearInterval(timer) | ||
const idx = connections.indexOf(conn) | ||
if (idx === -1) return | ||
connections.splice(idx, 1) | ||
}) | ||
|
||
const timer = setInterval(() => { | ||
conn.write(Buffer.alloc(10)) | ||
}, 100) | ||
conn.write(Buffer.alloc(10)) | ||
}) | ||
} | ||
for (let i = 0; i < NUM_TOPICS; i++) { | ||
const topic = crypto.randomBytes(32) | ||
topics.push(topic) | ||
} | ||
|
||
for (const topic of topics) { | ||
const numSwarms = Math.round(rand() * NUM_SWARMS) | ||
const topicSwarms = [] | ||
for (let i = 0; i < numSwarms; i++) { | ||
topicSwarms.push(swarms[Math.floor(rand() * NUM_SWARMS)]) | ||
} | ||
for (const swarm of topicSwarms) { | ||
const peers = peersBySwarm.get(swarm) | ||
for (const s of topicSwarms) { | ||
if (swarm === s) continue | ||
peers.add(s.keyPair.publicKey.toString('hex')) | ||
} | ||
await swarm.join(topic).flushed() | ||
} | ||
} | ||
|
||
await Promise.all(swarms.map(s => s.flush())) | ||
await timeout(STARTUP_DURATION) | ||
|
||
// Randomly destroy connections during the chaos period. | ||
for (let i = 0; i < NUM_FORCE_DISCONNECTS; i++) { | ||
const timeout = Math.floor(rand() * CHAOS_DURATION) // Leave a lot of room at the end for reestablishing connections (timeouts) | ||
setTimeout(() => { | ||
if (!connections.length) return | ||
const idx = Math.floor(rand() * connections.length) | ||
const conn = connections[idx] | ||
conn.destroy() | ||
}, timeout) | ||
} | ||
|
||
await timeout(TEST_DURATION) // Wait for the chaos to resolve | ||
|
||
for (const [swarm, expectedPeers] of peersBySwarm) { | ||
t.same(swarm.connections.size, expectedPeers.size, 'swarm has the correct number of connections') | ||
const missingKeys = [] | ||
for (const conn of swarm.connections) { | ||
const key = conn.remotePublicKey.toString('hex') | ||
if (!expectedPeers.has(key)) missingKeys.push(key) | ||
} | ||
t.same(missingKeys.length, 0, 'swarm is not missing any expected peers') | ||
} | ||
|
||
await destroyAll(...swarms) | ||
t.end() | ||
}) | ||
|
||
function noop () {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
const { timeout } = require('nonsynchronous') | ||
|
||
const Hyperswarm = require('..') | ||
const { test, destroyAll } = require('./helpers') | ||
|
||
const CONNECTION_TIMEOUT = 100 | ||
const BACKOFFS = [ | ||
100, | ||
200, | ||
300 | ||
] | ||
|
||
test('firewalled server - bad client is rejected', async (bootstrap, t) => { | ||
const swarm1 = new Hyperswarm({ bootstrap, backoffs: BACKOFFS, jitter: 0 }) | ||
const swarm2 = new Hyperswarm({ | ||
bootstrap, | ||
backoffs: BACKOFFS, | ||
jitter: 0, | ||
firewall: remotePublicKey => { | ||
return !remotePublicKey.equals(swarm1.keyPair.publicKey) | ||
} | ||
}) | ||
|
||
let serverConnections = 0 | ||
swarm2.on('connection', () => serverConnections++) | ||
|
||
const topic = Buffer.alloc(32).fill('hello world') | ||
await swarm2.join(topic, { client: false, server: true }).flushed() | ||
|
||
swarm1.join(topic, { client: true, server: false }) | ||
|
||
await timeout(CONNECTION_TIMEOUT) | ||
|
||
t.same(serverConnections, 0, 'server did not receive an incoming connection') | ||
|
||
await destroyAll(swarm1, swarm2) | ||
t.end() | ||
}) | ||
|
||
test('firewalled client - bad server is rejected', async (bootstrap, t) => { | ||
const swarm1 = new Hyperswarm({ bootstrap, backoffs: BACKOFFS, jitter: 0 }) | ||
const swarm2 = new Hyperswarm({ | ||
bootstrap, | ||
backoffs: BACKOFFS, | ||
jitter: 0, | ||
firewall: remotePublicKey => { | ||
return !remotePublicKey.equals(swarm1.keyPair.publicKey) | ||
} | ||
}) | ||
|
||
let clientConnections = 0 | ||
swarm2.on('connection', () => clientConnections++) | ||
|
||
const topic = Buffer.alloc(32).fill('hello world') | ||
await swarm1.join(topic, { client: false, server: true }).flushed() | ||
|
||
swarm2.join(topic, { client: true, server: false }) | ||
|
||
await timeout(CONNECTION_TIMEOUT) | ||
|
||
t.same(clientConnections, 0, 'client did not receive an incoming connection') | ||
|
||
await destroyAll(swarm1, swarm2) | ||
t.end() | ||
}) | ||
|
||
test('firewalled server - rejection does not trigger retry cascade', async (bootstrap, t) => { | ||
const swarm1 = new Hyperswarm({ bootstrap, backoffs: BACKOFFS, jitter: 0 }) | ||
|
||
let firewallCalls = 0 | ||
const swarm2 = new Hyperswarm({ | ||
bootstrap, | ||
backoffs: BACKOFFS, | ||
jitter: 0, | ||
firewall: remotePublicKey => { | ||
firewallCalls++ | ||
return !remotePublicKey.equals(swarm1.keyPair.publicKey) | ||
} | ||
}) | ||
|
||
let serverConnections = 0 | ||
swarm2.on('connection', () => serverConnections++) | ||
|
||
const topic = Buffer.alloc(32).fill('hello world') | ||
await swarm2.join(topic).flushed() | ||
|
||
swarm1.join(topic) | ||
|
||
await timeout(BACKOFFS[2] * 5) // Wait for many retries -- there should only be 3 | ||
|
||
t.same(serverConnections, 0, 'server did not receive an incoming connection') | ||
t.same(firewallCalls, 3, 'client retried 3 times') | ||
|
||
await destroyAll(swarm1, swarm2) | ||
t.end() | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.