diff --git a/lib/announcer.js b/lib/announcer.js index 42f7d8ef..240c4cb0 100644 --- a/lib/announcer.js +++ b/lib/announcer.js @@ -8,6 +8,8 @@ const Persistent = require('./persistent') const { COMMANDS } = require('./constants') const MIN_ACTIVE = 3 +const MAX_ANNOUNCE_RELAYS = 3 +const RTT_CANDIDATE_WINDOW = 8 module.exports = class Announcer { constructor(dht, keyPair, target, opts = {}) { @@ -296,8 +298,12 @@ function resolved(ps) { } function pickBest(replies) { - // TODO: pick the ones closest to us RTT wise - return replies.slice(0, 3) + return replies + .slice(0, RTT_CANDIDATE_WINDOW) + .map((reply, index) => ({ reply, index })) + .sort((a, b) => a.reply.rtt - b.reply.rtt || a.index - b.index) + .slice(0, MAX_ANNOUNCE_RELAYS) + .map(({ reply }) => reply) } function noop() {} diff --git a/test/announces.js b/test/announces.js index 6a144099..cbd17069 100644 --- a/test/announces.js +++ b/test/announces.js @@ -5,6 +5,7 @@ const messages = require('../lib/messages.js') const { swarm, toArray } = require('./helpers') const DHT = require('../') const HyperDHT = require('../') +const Announcer = require('../lib/announcer') const { COMMANDS } = require('../lib/constants.js') test('server listen and findPeer', async function (t) { @@ -193,6 +194,56 @@ test('server announces relay addrs', async function (t) { } }) +test('announcer prefers lower rtt among close-enough replies', async function (t) { + const keyPair = DHT.keyPair() + const target = DHT.hash(Buffer.from('rtt-ranked-announces')) + const rtts = [80, 70, 20, 20, 40, 20, 60, 10, 1, 2] + const replies = rtts.map((rtt, i) => { + return { + token: b4a.alloc(32, i), + rtt, + from: { + id: b4a.alloc(32, i + 1), + host: '127.0.0.1', + port: 10000 + i + }, + to: { + host: '127.0.0.1', + port: 20000 + i + } + } + }) + const announced = [] + const dht = { + firewalled: true, + destroyed: false, + findPeer() { + return { + closestReplies: replies, + closestNodes: replies.map((reply) => reply.from), + finished: () => Promise.resolve() + } + }, + request(req, to) { + t.is(req.command, COMMANDS.ANNOUNCE) + announced.push(to.port) + return Promise.resolve({ error: 0 }) + } + } + const announcer = new Announcer(dht, keyPair, target, { + signAnnounce: () => b4a.alloc(64) + }) + + await announcer._update() + + t.alike( + announced, + [10007, 10002, 10003], + 'announced to lowest-rtt replies and kept reply order for equal rtt' + ) + t.absent(announced.includes(10008), 'ignored lower-rtt reply outside the candidate window') +}) + test('connect when we relay ourself', async function (t) { const testnet = await swarm(t)