1781 lines
50 KiB
JavaScript
1781 lines
50 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const path = require('path');
|
|
const fs = require('bfile');
|
|
const Logger = require('blgr');
|
|
const IP = require('binet');
|
|
const base32 = require('bcrypto/lib/encoding/base32');
|
|
const secp256k1 = require('bcrypto/lib/secp256k1');
|
|
const random = require('bcrypto/lib/random');
|
|
const common = require('./util/common');
|
|
const util = require('../lib/utils/util');
|
|
const Network = require('../lib/protocol/network');
|
|
const NetAddress = require('../lib/net/netaddress');
|
|
const HostList = require('../lib/net/hostlist');
|
|
const {HostEntry} = HostList;
|
|
|
|
const regtest = Network.get('regtest');
|
|
const mainnet = Network.get('main');
|
|
|
|
/*
|
|
* Some configurations for the tests
|
|
*/
|
|
|
|
function getHostsFromLocals(addresses, opts) {
|
|
const hosts = new HostList(opts);
|
|
|
|
for (const [addr, score] of addresses)
|
|
hosts.pushLocal(addr, score);
|
|
|
|
return hosts;
|
|
}
|
|
|
|
// flat
|
|
function getFreshEntries(hosts) {
|
|
const naddrs = [];
|
|
|
|
for (const bucket of hosts.fresh)
|
|
naddrs.push(...bucket.values());
|
|
|
|
return naddrs;
|
|
};
|
|
|
|
function getUsedEntries(hosts) {
|
|
const naddrs = [];
|
|
|
|
for (const bucket of hosts.used)
|
|
naddrs.push(...bucket.toArray());
|
|
|
|
return naddrs;
|
|
}
|
|
|
|
function getRandomNetAddr(network = regtest) {
|
|
return NetAddress.fromHostname(getRandomIPv4(), network);
|
|
}
|
|
|
|
function add2bucket(hosts, bucketIndex, entry, fresh = true) {
|
|
if (fresh) {
|
|
assert(bucketIndex < hosts.maxFreshBuckets);
|
|
entry.refCount++;
|
|
hosts.totalFresh++;
|
|
hosts.map.set(entry.key(), entry);
|
|
hosts.fresh[bucketIndex].set(entry.key(), entry);
|
|
return;
|
|
}
|
|
|
|
assert(bucketIndex < hosts.maxUsedBuckets);
|
|
assert(entry.refCount === 0);
|
|
entry.used = true;
|
|
hosts.map.set(entry.key(), entry);
|
|
hosts.used[bucketIndex].push(entry);
|
|
hosts.totalUsed++;
|
|
};
|
|
|
|
describe('Net HostList', function() {
|
|
it('should parse options', () => {
|
|
const network = regtest;
|
|
const logger = Logger.global;
|
|
const resolve = () => {};
|
|
const banTime = 100;
|
|
const seeds = ['127.1.1.1', 'example.com'];
|
|
const nodes = ['127.2.2.2'];
|
|
const host = '127.0.0.1';
|
|
const port = regtest.port;
|
|
const publicHost = getRandomIPv4();
|
|
const publicPort = regtest.port;
|
|
const publicBrontidePort = regtest.brontidePort;
|
|
const identityKey = secp256k1.privateKeyGenerate();
|
|
const pubIdentityKey = secp256k1.publicKeyCreate(identityKey);
|
|
const services = 1001;
|
|
const onion = false;
|
|
const brontideOnly = false;
|
|
const memory = true;
|
|
const prefix = '/tmp/directory';
|
|
const filename = path.join(prefix, 'custom.json');
|
|
const flushInterval = 2000;
|
|
|
|
const options = {
|
|
network,
|
|
logger,
|
|
resolve,
|
|
banTime,
|
|
seeds,
|
|
nodes,
|
|
host,
|
|
port,
|
|
publicHost,
|
|
publicPort,
|
|
publicBrontidePort,
|
|
identityKey,
|
|
services,
|
|
onion,
|
|
brontideOnly,
|
|
memory,
|
|
prefix,
|
|
filename,
|
|
flushInterval
|
|
};
|
|
|
|
const hosts = new HostList(options);
|
|
|
|
assert.strictEqual(hosts.network, network);
|
|
|
|
// Hostlist will use context('hostlist') instead.
|
|
// assert.strictEqual(hostlist.logger, logger);
|
|
assert.strictEqual(hosts.resolve, resolve);
|
|
assert.strictEqual(hosts.options.banTime, banTime);
|
|
|
|
// seeds are still stored in options until initAdd.
|
|
assert.deepStrictEqual(hosts.options.seeds, seeds);
|
|
|
|
// Nodes are still stored in options until initAdd.
|
|
assert.deepStrictEqual(hosts.options.nodes, nodes);
|
|
|
|
// Host:port will become local node after initAdd.
|
|
assert.strictEqual(hosts.options.host, host);
|
|
assert.strictEqual(hosts.options.port, port);
|
|
|
|
{
|
|
// public address
|
|
const address = new NetAddress({
|
|
host: publicHost,
|
|
port: publicPort,
|
|
services
|
|
});
|
|
|
|
assert.strictEqual(hosts.address.equal(address), true);
|
|
assert.strictEqual(hosts.address.services, services);
|
|
}
|
|
|
|
{
|
|
// brontide Address
|
|
const address = new NetAddress({
|
|
host: publicHost,
|
|
port: publicBrontidePort,
|
|
key: pubIdentityKey,
|
|
services
|
|
});
|
|
|
|
assert.strictEqual(hosts.brontide.equal(address), true);
|
|
assert.strictEqual(hosts.brontide.services, services);
|
|
assert.bufferEqual(hosts.brontide.getKey(), pubIdentityKey);
|
|
}
|
|
|
|
assert.strictEqual(hosts.options.onion, onion);
|
|
assert.strictEqual(hosts.options.brontideOnly, brontideOnly);
|
|
assert.strictEqual(hosts.options.memory, memory);
|
|
assert.strictEqual(hosts.options.prefix, prefix);
|
|
assert.strictEqual(hosts.options.filename, filename);
|
|
assert.strictEqual(hosts.options.flushInterval, flushInterval);
|
|
|
|
// Prefix check
|
|
{
|
|
const hostlist = new HostList({
|
|
prefix
|
|
});
|
|
|
|
assert.strictEqual(hostlist.options.filename,
|
|
path.join(prefix, 'hosts.json'));
|
|
}
|
|
});
|
|
|
|
it('should init add ips', () => {
|
|
const network = regtest;
|
|
const host = '127.0.0.1';
|
|
const port = regtest.port;
|
|
const publicHost = '1.1.1.1';
|
|
const publicPort = regtest.port;
|
|
const publicBrontidePort = regtest.brontidePort;
|
|
const identityKey = secp256k1.privateKeyGenerate();
|
|
const pubIdentityKey = secp256k1.publicKeyCreate(identityKey);
|
|
const seeds = ['127.1.1.1', 'example.com',
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@127.3.3.3'];
|
|
const nodes = ['127.2.2.2'];
|
|
|
|
const hosts = new HostList({
|
|
network,
|
|
host,
|
|
port,
|
|
publicHost,
|
|
publicPort,
|
|
publicBrontidePort,
|
|
identityKey,
|
|
seeds,
|
|
nodes
|
|
});
|
|
|
|
const ipGetPublic = IP.getPublic;
|
|
|
|
const interfaceIPs = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
IP.getPublic = () => interfaceIPs;
|
|
|
|
hosts.initAdd();
|
|
assert.strictEqual(hosts.added, true);
|
|
|
|
// It's behind a check.
|
|
hosts.initAdd();
|
|
|
|
IP.getPublic = ipGetPublic;
|
|
|
|
{
|
|
// one for brontinde public and one plaintext public
|
|
// two from interfaces
|
|
assert.strictEqual(hosts.local.size, 4);
|
|
const plaintextHost = IP.toHost(publicHost, publicPort);
|
|
const brontideHost = IP.toHost(
|
|
publicHost,
|
|
publicBrontidePort,
|
|
pubIdentityKey
|
|
);
|
|
|
|
const interfaceHosts = [
|
|
IP.toHost(interfaceIPs[0], publicPort),
|
|
IP.toHost(interfaceIPs[1], publicPort)
|
|
];
|
|
|
|
{
|
|
assert(hosts.local.has(plaintextHost));
|
|
const local = hosts.local.get(plaintextHost);
|
|
assert.strictEqual(local.score, HostList.scores.MANUAL);
|
|
}
|
|
|
|
{
|
|
assert(hosts.local.has(brontideHost));
|
|
const local = hosts.local.get(brontideHost);
|
|
assert.strictEqual(local.score, HostList.scores.MANUAL);
|
|
}
|
|
|
|
for (const ihost of interfaceHosts) {
|
|
assert(hosts.local.has(ihost));
|
|
const local = hosts.local.get(ihost);
|
|
|
|
assert.strictEqual(local.score, HostList.scores.IF);
|
|
}
|
|
}
|
|
|
|
// After initAdd();
|
|
// 127.1.1.1 - becomes normal peer
|
|
// example.com - will become dnsSeed
|
|
assert.strictEqual(hosts.dnsSeeds.length, 1);
|
|
assert.deepStrictEqual(hosts.dnsSeeds[0], IP.fromHost(seeds[1]));
|
|
|
|
// Check 127.1.1.1 and 127.3.3.3
|
|
assert(hosts.map.has(`${seeds[0]}:${regtest.port}`));
|
|
assert(hosts.map.has(`127.3.3.3:${regtest.brontidePort}`));
|
|
|
|
// Check nodes have been added (127.2.2.2)
|
|
assert(hosts.map.has(`${nodes[0]}:${regtest.port}`));
|
|
assert.strictEqual(hosts.map.size, 3);
|
|
});
|
|
|
|
it('should ban/unban', () => {
|
|
const hosts = new HostList();
|
|
|
|
const banIPs = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
assert.strictEqual(hosts.banned.size, 0);
|
|
|
|
{
|
|
for (const ip of banIPs)
|
|
hosts.ban(ip);
|
|
|
|
assert.strictEqual(hosts.banned.size, banIPs.length);
|
|
}
|
|
|
|
{
|
|
for (const ip of banIPs)
|
|
hosts.unban(ip);
|
|
|
|
assert.strictEqual(hosts.banned.size, 0);
|
|
}
|
|
|
|
{
|
|
assert.strictEqual(hosts.banned.size, 0);
|
|
for (const ip of banIPs)
|
|
hosts.ban(ip);
|
|
assert.strictEqual(hosts.banned.size, banIPs.length);
|
|
|
|
for (const ip of banIPs)
|
|
assert(hosts.isBanned(ip));
|
|
|
|
hosts.clearBanned();
|
|
|
|
for (const ip of banIPs)
|
|
assert.strictEqual(hosts.isBanned(ip), false);
|
|
}
|
|
|
|
// ban time
|
|
{
|
|
assert.strictEqual(hosts.banned.size, 0);
|
|
|
|
for (const ip of banIPs)
|
|
hosts.ban(ip);
|
|
|
|
// change ban time for the first IP.
|
|
const banExpired = banIPs[0];
|
|
hosts.banned.set(banExpired, util.now() - hosts.options.banTime - 1);
|
|
|
|
const [, ...stillBanned] = banIPs;
|
|
for (const ip of stillBanned)
|
|
assert(hosts.isBanned(ip));
|
|
|
|
assert.strictEqual(hosts.banned.size, banIPs.length);
|
|
assert.strictEqual(hosts.isBanned(banExpired), false);
|
|
assert.strictEqual(hosts.banned.size, banIPs.length - 1);
|
|
}
|
|
});
|
|
|
|
describe('nodes and seeds', function() {
|
|
it('should add/set nodes/seeds', () => {
|
|
// we need 3.
|
|
const hosts = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
const key = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
|
|
|
const tests = [
|
|
// DNS Nodes
|
|
{
|
|
host: 'example.com',
|
|
hostname: 'example.com',
|
|
expected: {
|
|
addr: null,
|
|
dnsNodes: 1,
|
|
nodes: 0,
|
|
map: 0
|
|
}
|
|
},
|
|
// HSD Node w/o port - plaintext
|
|
{
|
|
host: hosts[0],
|
|
hostname: hosts[0],
|
|
expected: {
|
|
addr: { port: mainnet.port, host: hosts[0] },
|
|
dnsNodes: 0,
|
|
nodes: 1,
|
|
map: 1
|
|
}
|
|
},
|
|
// HSD Node w/o port - brontide
|
|
{
|
|
host: hosts[1],
|
|
hostname: `${key}@${hosts[1]}`,
|
|
expected: {
|
|
addr: { host: hosts[1], port: mainnet.brontidePort },
|
|
dnsNodes: 0,
|
|
nodes: 1,
|
|
map: 1
|
|
}
|
|
},
|
|
// HSD Node with port
|
|
{
|
|
host: hosts[2],
|
|
hostname: `${hosts[2]}:${mainnet.port + 1}`,
|
|
expected: {
|
|
addr: { host: hosts[2], port: mainnet.port + 1 },
|
|
dnsNodes: 0,
|
|
nodes: 1,
|
|
map: 1
|
|
}
|
|
}
|
|
];
|
|
|
|
const allHosts = tests.map(t => t.hostname);
|
|
const sumExpected = tests.reduce((p, c) => {
|
|
p.dnsNodes += c.expected.dnsNodes;
|
|
p.nodes += c.expected.nodes;
|
|
p.map += c.expected.map;
|
|
return p;
|
|
}, {
|
|
dnsNodes: 0,
|
|
nodes: 0,
|
|
map: 0
|
|
});
|
|
|
|
for (const test of tests) {
|
|
const hosts = new HostList();
|
|
const {expected} = test;
|
|
|
|
const addr = hosts.addNode(test.hostname);
|
|
|
|
if (expected.addr == null)
|
|
assert.strictEqual(addr, null);
|
|
|
|
if (expected.addr != null) {
|
|
assert.strictEqual(addr.host, expected.addr.host);
|
|
assert.strictEqual(addr.port, expected.addr.port);
|
|
}
|
|
|
|
assert.strictEqual(hosts.dnsNodes.length, expected.dnsNodes);
|
|
assert.strictEqual(hosts.nodes.length, expected.nodes);
|
|
assert.strictEqual(hosts.map.size, expected.map);
|
|
}
|
|
|
|
// set all nodes
|
|
{
|
|
const hosts = new HostList();
|
|
hosts.setNodes(allHosts);
|
|
assert.strictEqual(hosts.dnsNodes.length, sumExpected.dnsNodes);
|
|
assert.strictEqual(hosts.nodes.length, sumExpected.nodes);
|
|
assert.strictEqual(hosts.map.size, sumExpected.map);
|
|
}
|
|
|
|
for (const test of tests) {
|
|
const hosts = new HostList();
|
|
const {expected} = test;
|
|
|
|
const addr = hosts.addSeed(test.hostname);
|
|
|
|
if (expected.addr == null)
|
|
assert.strictEqual(addr, null);
|
|
|
|
if (expected.addr != null) {
|
|
assert.strictEqual(addr.host, expected.addr.host);
|
|
assert.strictEqual(addr.port, expected.addr.port);
|
|
}
|
|
|
|
assert.strictEqual(hosts.dnsSeeds.length, expected.dnsNodes);
|
|
assert.strictEqual(hosts.map.size, expected.map);
|
|
}
|
|
|
|
{
|
|
const hosts = new HostList();
|
|
|
|
hosts.setSeeds(allHosts);
|
|
assert.strictEqual(hosts.dnsSeeds.length, sumExpected.dnsNodes);
|
|
assert.strictEqual(hosts.map.size, sumExpected.map);
|
|
}
|
|
});
|
|
|
|
it('should remove node', () => {
|
|
const hosts = new HostList();
|
|
const key = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
|
const ips = [getRandomIPv4(), getRandomIPv4(), getRandomIPv4()];
|
|
const nodes = [
|
|
ips[0],
|
|
`${key}@${ips[1]}`,
|
|
`${ips[2]}:1000`,
|
|
`${ips[2]}:2000`
|
|
];
|
|
|
|
for (const node of nodes)
|
|
assert(hosts.addNode(node));
|
|
|
|
assert.strictEqual(hosts.nodes.length, nodes.length);
|
|
|
|
for (const node of nodes.reverse())
|
|
assert(hosts.removeNode(node));
|
|
|
|
assert.strictEqual(hosts.removeNode(nodes[0]), false);
|
|
assert.strictEqual(hosts.nodes.length, 0);
|
|
});
|
|
});
|
|
|
|
describe('Local addresses', function() {
|
|
it('should add push/local addresses', () => {
|
|
const services = 1000;
|
|
const hosts = new HostList({ services });
|
|
|
|
const unroutable = [
|
|
'127.0.0.1',
|
|
'127.1.1.1',
|
|
'192.168.1.1',
|
|
'10.10.10.10'
|
|
];
|
|
|
|
const routable = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
assert.strictEqual(hosts.local.size, 0);
|
|
|
|
// unroutable local addresses must not get added.
|
|
for (const host of unroutable) {
|
|
const port = regtest.port;
|
|
const score = HostList.scores.NONE;
|
|
const res = hosts.addLocal(host, port, null, score);
|
|
assert.strictEqual(res, false);
|
|
}
|
|
|
|
assert.strictEqual(hosts.local.size, 0);
|
|
|
|
const SCORE = HostList.scores.MANUAL;
|
|
// these must get added.
|
|
for (const host of routable) {
|
|
const port = regtest.port;
|
|
const res1 = hosts.addLocal(host, port, SCORE);
|
|
assert.strictEqual(res1, true);
|
|
|
|
// add brontide versions
|
|
const bport = regtest.brontidePort;
|
|
const res2 = hosts.addLocal(host, bport, SCORE);
|
|
assert.strictEqual(res2, true);
|
|
}
|
|
|
|
// one brontide and one plaintext
|
|
assert.strictEqual(hosts.local.size, routable.length * 2);
|
|
|
|
const added = hosts.addLocal(routable[0], regtest.port, SCORE);
|
|
assert.strictEqual(added, false);
|
|
|
|
for (const laddr of hosts.local.values()) {
|
|
assert.strictEqual(laddr.addr.services, services);
|
|
assert.strictEqual(laddr.score, SCORE);
|
|
assert.strictEqual(laddr.type, SCORE);
|
|
}
|
|
});
|
|
|
|
// w/o src it will only take into account the score.
|
|
it('should only get local w/o src if MANUAL', () => {
|
|
const services = 1000;
|
|
const port = regtest.port;
|
|
|
|
const addrs = [];
|
|
|
|
// w/o key
|
|
for (let i = HostList.scores.NONE; i < HostList.scores.MAX; i++) {
|
|
const address = new NetAddress({
|
|
host: getRandomIPv4(),
|
|
port: port,
|
|
services: services
|
|
});
|
|
addrs.push([address, i]);
|
|
}
|
|
|
|
// max but with key
|
|
addrs.push([new NetAddress({
|
|
host: getRandomIPv4(),
|
|
port: port,
|
|
services: services,
|
|
key: Buffer.alloc(33, 1)
|
|
}), HostList.scores.MAX]);
|
|
|
|
const manual = addrs[HostList.scores.MANUAL];
|
|
|
|
const hosts = getHostsFromLocals(addrs);
|
|
const local = hosts.getLocal();
|
|
|
|
assert.strictEqual(local, manual[0]);
|
|
});
|
|
|
|
it('should get local (score)', () => {
|
|
// NOTE: We ignore everything other than MANUAL type.
|
|
// - scores.IF - addresses from the network interfaces.
|
|
// - scores.BIND - Listening IP. (publicHost/publicPort is MANUAL)
|
|
// - scores.DNS - Domain that needs to be resolved.
|
|
// - scores.UPNP - UPNP discovered address.
|
|
|
|
const scores = HostList.scores;
|
|
|
|
// same NET type but different scores:
|
|
const hostsByScore = [
|
|
[getRandomIPv4(), scores.NONE],
|
|
[getRandomIPv4(), scores.IF],
|
|
[getRandomIPv4(), scores.BIND],
|
|
[getRandomIPv4(), scores.DNS],
|
|
[getRandomIPv4(), scores.UPNP],
|
|
[getRandomIPv4(), scores.MANUAL]
|
|
];
|
|
|
|
const naddrsByScore = hostsByScore.map(([h, s]) => {
|
|
return [getRandomNetAddr(), s];
|
|
});
|
|
|
|
// testnet/regtest
|
|
{
|
|
const hosts = getHostsFromLocals(naddrsByScore, { network: regtest });
|
|
const src = getRandomNetAddr();
|
|
const best = hosts.getLocal(src);
|
|
assert.strictEqual(best, naddrsByScore[scores.MANUAL][0]);
|
|
}
|
|
|
|
// mainnet
|
|
{
|
|
const hosts = getHostsFromLocals(naddrsByScore, { network: mainnet });
|
|
const src = getRandomNetAddr(mainnet);
|
|
const best = hosts.getLocal(src);
|
|
assert.strictEqual(best, naddrsByScore[scores.MANUAL][0]);
|
|
}
|
|
|
|
{
|
|
// everything below MANUAL is skipped.
|
|
const addrs = naddrsByScore.slice(scores.NONE, scores.UPNP);
|
|
const hosts = getHostsFromLocals(addrs, { network: mainnet });
|
|
const src = getRandomNetAddr(mainnet);
|
|
const best = hosts.getLocal(src);
|
|
assert.strictEqual(best, null);
|
|
}
|
|
});
|
|
|
|
it('should get local (reachability)', () => {
|
|
// If we have multiple public host/ports (e.g. IPv4 and Ipv6)
|
|
// depending who is connecting to us, we will choose different
|
|
// address to advertise.
|
|
|
|
// with src it will take into account the reachability score.
|
|
// See: binet.getReachability
|
|
// TLDR:
|
|
// UNREACHABLE = 0
|
|
// < DEFAULT = 1 -- non-ipv4 -> ipv4, onion -> ipv6, ...
|
|
// < TEREDO = 2 -- teredo -> teredo, teredo -> ipv6
|
|
// < IPV6_WEAK = 3 -- ipv4 -> ipv6 tunnels, ipv6 -> teredo
|
|
// < IPV4 = 4 -- ipv4 -> ipv4, ipv4 -> others
|
|
// < IPV6_STRONG = 5 -- ipv6 -> ipv6
|
|
// < PRIVATE = 6 -- ONION -> ONION
|
|
|
|
const {MANUAL} = HostList.scores;
|
|
|
|
// same score (MANUAL), different reachability:
|
|
// remote(src) => [ local(dest)... ] - sorted by reachability scores.
|
|
const reachabilityMap = {
|
|
// unreachable => anything - will be UNREACHABLE = 0.
|
|
[getRandomTEREDO()]: [
|
|
getRandomIPv4(), // DEFAULT = 1
|
|
getRandomOnion(), // DEFAULT = 1
|
|
getRandomTEREDO(), // TEREDO = 2
|
|
getRandomIPv6() // TEREDO = 2
|
|
],
|
|
[getRandomIPv4()]: [
|
|
getRandomIPv4(), // IPV4 = 4
|
|
getRandomOnion(), // IPV4 = 4
|
|
getRandomTEREDO(), // IPV4 = 4
|
|
getRandomIPv6() // IPV4 = 4
|
|
],
|
|
[getRandomIPv6()]: [
|
|
getRandomOnion(), // DEFAULT = 1
|
|
getRandomIPv4(), // DEFAULT = 1
|
|
getRandomTEREDO(), // IPV6_WEAK = 3
|
|
getRandomIPv6() // IPV6_STRONG = 5
|
|
],
|
|
[getRandomOnion()]: [
|
|
getRandomIPv4(), // DEFAULT = 1
|
|
getRandomTEREDO(), // DEFAULT = 1
|
|
getRandomIPv6(), // DEFAULT = 1
|
|
getRandomOnion() // PRIVATE = 6
|
|
]
|
|
};
|
|
|
|
for (const [rawSrc, rawDests] of Object.entries(reachabilityMap)) {
|
|
const dests = rawDests.map((dest) => {
|
|
return [NetAddress.fromHostname(dest, mainnet), MANUAL];
|
|
});
|
|
|
|
for (let i = 0; i < dests.length; i++) {
|
|
const addrs = dests.slice(0, i + 1);
|
|
const expected = addrs[addrs.length - 1];
|
|
|
|
// Because getLocal will choose first with the same score,
|
|
// we make the "best" choice (because of the sorting) at first.
|
|
addrs[addrs.length - 1] = addrs[0];
|
|
addrs[0] = expected;
|
|
|
|
const hosts = getHostsFromLocals(addrs);
|
|
const src = NetAddress.fromHostname(rawSrc, mainnet);
|
|
const best = hosts.getLocal(src);
|
|
|
|
assert.strictEqual(best, expected[0]);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should not get local (skip brontide)', () => {
|
|
const {MANUAL} = HostList.scores;
|
|
|
|
// skip with key
|
|
const src = getRandomIPv4();
|
|
const KEY = base32.encode(Buffer.alloc(33, 1));
|
|
const rawDests = [
|
|
`${KEY}@${getRandomIPv4()}:${mainnet.brontidePort}`,
|
|
`${KEY}@${getRandomIPv4()}:${mainnet.brontidePort}`
|
|
];
|
|
|
|
const dests = rawDests.map((d) => {
|
|
return [NetAddress.fromHostname(d, mainnet), MANUAL];
|
|
});
|
|
|
|
const hosts = getHostsFromLocals(dests);
|
|
const best = hosts.getLocal(src);
|
|
assert.strictEqual(best, null);
|
|
});
|
|
|
|
it('should mark local', () => {
|
|
const {scores} = HostList;
|
|
|
|
const rawDests = [
|
|
[getRandomIPv4(), scores.UPNP],
|
|
[getRandomIPv4(), scores.MANUAL]
|
|
];
|
|
|
|
const dests = rawDests.map(([h, s]) => {
|
|
return [NetAddress.fromHostname(h, regtest), s];
|
|
});
|
|
|
|
const hosts = getHostsFromLocals(dests, { network: regtest });
|
|
|
|
{
|
|
const addr = getRandomNetAddr();
|
|
const marked = hosts.markLocal(addr);
|
|
|
|
assert.strictEqual(marked, false);
|
|
}
|
|
|
|
{
|
|
// we should get MANUAL only
|
|
const addr = getRandomNetAddr();
|
|
const local = hosts.getLocal(addr);
|
|
assert.strictEqual(local, dests[1][0]);
|
|
}
|
|
|
|
{
|
|
// with markLocal UPNP should get the same score (type remains).
|
|
hosts.markLocal(dests[0][0]);
|
|
const addr = getRandomNetAddr();
|
|
const local = hosts.getLocal(addr);
|
|
assert.strictEqual(local, dests[0][0]);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Fresh bucket', function() {
|
|
it('should add fresh address', () => {
|
|
{
|
|
const hosts = new HostList();
|
|
|
|
// fresh, w/o src, not in the buckets
|
|
const addr = getRandomNetAddr();
|
|
|
|
assert.strictEqual(hosts.totalFresh, 0);
|
|
assert.strictEqual(hosts.needsFlush, false);
|
|
assert.strictEqual(hosts.map.size, 0);
|
|
assert.strictEqual(getFreshEntries(hosts).length, 0);
|
|
|
|
hosts.add(addr);
|
|
|
|
assert.strictEqual(hosts.totalFresh, 1);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
assert.strictEqual(hosts.map.size, 1);
|
|
|
|
const freshEntries = getFreshEntries(hosts);
|
|
assert.strictEqual(freshEntries.length, 1);
|
|
|
|
const entry = freshEntries[0];
|
|
assert.strictEqual(entry.addr, addr, 'Entry addr is not correct.');
|
|
assert.strictEqual(entry.src, hosts.address, 'Entry src is not correct.');
|
|
}
|
|
|
|
{
|
|
const hosts = new HostList();
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
|
|
hosts.add(addr, src);
|
|
const freshEntries = getFreshEntries(hosts);
|
|
assert.strictEqual(freshEntries.length, 1);
|
|
|
|
const entry = freshEntries[0];
|
|
assert.strictEqual(entry.addr, addr, 'Entry addr is not correct.');
|
|
assert.strictEqual(entry.src, src, 'Entry src is not correct.');
|
|
assert.strictEqual(entry.refCount, 1);
|
|
assert.strictEqual(hosts.map.size, 1);
|
|
}
|
|
});
|
|
|
|
it('should add address (limits)', () => {
|
|
// Full Bucket?
|
|
{
|
|
const hosts = new HostList();
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
|
|
let evicted = false;
|
|
|
|
// always return first bucket for this test.
|
|
hosts.freshBucket = function() {
|
|
return this.fresh[0];
|
|
};
|
|
|
|
hosts.evictFresh = function() {
|
|
evicted = true;
|
|
};
|
|
|
|
// Fill first bucket.
|
|
for (let i = 0; i < hosts.maxEntries; i++) {
|
|
const addr = getRandomNetAddr();
|
|
const added = hosts.add(addr, src);
|
|
assert.strictEqual(added, true);
|
|
assert.strictEqual(evicted, false);
|
|
}
|
|
|
|
const added = hosts.add(addr, src);
|
|
assert.strictEqual(added, true);
|
|
assert.strictEqual(evicted, true);
|
|
}
|
|
|
|
// Don't insert if entry is in a bucket.
|
|
{
|
|
const hosts = new HostList();
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
const entry = new HostList.HostEntry(addr, src);
|
|
|
|
// insert entry in every bucket for this test.
|
|
for (const bucket of hosts.fresh)
|
|
bucket.set(entry.key(), entry);
|
|
|
|
const added = hosts.add(addr, src);
|
|
assert.strictEqual(added, false);
|
|
}
|
|
});
|
|
|
|
it('should add seen address', () => {
|
|
const hosts = new HostList();
|
|
|
|
// make sure we don't insert into the same bucket twice. (refcount test)
|
|
let index = 0;
|
|
hosts.freshBucket = function () {
|
|
return this.fresh[index++];
|
|
};
|
|
|
|
// get addr clone that can be added (Online requirements)
|
|
const cloneAddr = (addr) => {
|
|
const addr2 = addr.clone();
|
|
// just make sure this is < 3 * 60 * 60 (Online requirements
|
|
// with & without penalty)
|
|
addr2.time = util.now() + 60 * 60;
|
|
return addr2;
|
|
};
|
|
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
addr.services = 0x01;
|
|
const added = hosts.add(addr, src);
|
|
assert.strictEqual(added, true);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
hosts.needsFlush = false;
|
|
|
|
const entries = getFreshEntries(hosts);
|
|
const entry = entries[0];
|
|
assert.strictEqual(entries.length, 1);
|
|
assert.strictEqual(entry.addr.services, 0x01);
|
|
|
|
// don't update - no new info (service will always get updated.)
|
|
{
|
|
const addr2 = addr.clone();
|
|
addr2.services = 0x02;
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
|
|
const entries = getFreshEntries(hosts);
|
|
const entry = entries[0];
|
|
assert.strictEqual(entries.length, 1);
|
|
assert.strictEqual(entry.addr.services, 0x03);
|
|
assert.strictEqual(hosts.needsFlush, false);
|
|
}
|
|
|
|
// update refCount. (we only have 1 refCount, increase up to 8)
|
|
{
|
|
const srcs = [];
|
|
for (let i = 0; i < 7; i++)
|
|
srcs.push(getRandomNetAddr());
|
|
|
|
const factors = [];
|
|
const _random = hosts.random;
|
|
const random = function (factor) {
|
|
factors.push(factor);
|
|
return 0;
|
|
};
|
|
hosts.random = random;
|
|
|
|
const addr2 = cloneAddr(addr);
|
|
|
|
// when we have 1 ref, so probability of adding second one
|
|
// is 50% (1/2).
|
|
// then we have 2 refs, probability of adding will be 25% (1/4).
|
|
// ... until we have 8 refs. (last one being 1/128)
|
|
// We use different SRC for the same host, so we don't get the
|
|
// same bucket.
|
|
let added = 0;
|
|
for (let i = 0; i < 7; i++) {
|
|
const res = hosts.add(addr2, srcs[i]);
|
|
|
|
// our custom random method always returns 0.
|
|
assert.strictEqual(res, true);
|
|
added++;
|
|
}
|
|
|
|
// make sure factors are calculated properly.
|
|
assert.strictEqual(factors.length, 7);
|
|
|
|
for (let i = 0; i < 7; i++)
|
|
assert.strictEqual(factors[i], 1 << (i + 1));
|
|
|
|
// at this point address should be in another bucket as well.
|
|
assert.strictEqual(added, 7);
|
|
assert.strictEqual(entry.refCount, 8);
|
|
const entries = getFreshEntries(hosts);
|
|
assert.strictEqual(entries.length, 8);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
hosts.needsFlush = false;
|
|
hosts.random = _random;
|
|
}
|
|
|
|
// should fail with max ref
|
|
{
|
|
const _refCount = entry.refCount;
|
|
entry.refCount = HostList.MAX_REFS;
|
|
const addr2 = cloneAddr(addr);
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
entry.refCount = _refCount;
|
|
assert.strictEqual(hosts.needsFlush, false);
|
|
}
|
|
|
|
// should fail if it's used
|
|
{
|
|
entry.used = true;
|
|
const addr2 = cloneAddr(addr);
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(hosts.needsFlush, false);
|
|
entry.used = false;
|
|
}
|
|
});
|
|
|
|
it('should add address (update time)', () => {
|
|
const getHosts = (time) => {
|
|
const hosts = new HostList();
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
|
|
if (time)
|
|
addr.time = time;
|
|
|
|
const added = hosts.add(addr, src);
|
|
assert.strictEqual(added, true);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
hosts.needsFlush = false;
|
|
|
|
const entries = getFreshEntries(hosts);
|
|
assert.strictEqual(entries.length, 1);
|
|
const entry = hosts.map.get(addr.hostname);
|
|
|
|
// make sure we stop after updating time.
|
|
entries[0].used = true;
|
|
|
|
return [hosts, entry, addr, src];
|
|
};
|
|
|
|
// Update time - Online?
|
|
{
|
|
// a week ago
|
|
const [hosts, entry, addr, src] = getHosts(util.now() - 7 * 24 * 60 * 60);
|
|
const addr2 = addr.clone();
|
|
|
|
// a day ago (interval is a day,
|
|
// so we update if a day and 2 hrs have passed).
|
|
addr2.time = util.now() - 24 * 60 * 60;
|
|
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(entry.addr.time, addr2.time);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
}
|
|
|
|
{
|
|
// a day ago
|
|
const [hosts, entry, addr, src] = getHosts(util.now() - 24 * 60 * 60);
|
|
|
|
const addr2 = addr.clone();
|
|
|
|
// now (interval becomes an hour, so instead we update if 3 hrs passed.
|
|
addr2.time = util.now();
|
|
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(entry.addr.time, addr2.time);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
}
|
|
|
|
// Don't update
|
|
{
|
|
// a week ago
|
|
const weekAgo = util.now() - 7 * 24 * 60 * 60;
|
|
const sixDaysAgo = util.now() - 6 * 24 * 60 * 60 + 1; // and a second
|
|
|
|
const [hosts, entry, addr, src] = getHosts(weekAgo);
|
|
|
|
const addr2 = addr.clone();
|
|
// 6 days ago (exactly 24 hrs after) because 2 hrs is penalty,
|
|
// we don't update.
|
|
addr2.time = sixDaysAgo;
|
|
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(entry.addr.time, weekAgo);
|
|
assert.strictEqual(hosts.needsFlush, false);
|
|
}
|
|
|
|
// Update, because we are the ones inserting.
|
|
{
|
|
// a week ago
|
|
const weekAgo = util.now() - 7 * 24 * 60 * 60;
|
|
const sixDaysAgo = util.now() - 6 * 24 * 60 * 60 + 1; // and a second
|
|
|
|
const [hosts, entry, addr] = getHosts(weekAgo);
|
|
|
|
const addr2 = addr.clone();
|
|
// 6 days ago (exactly 24 hrs after) because 2 hrs is penalty,
|
|
// we don't update.
|
|
addr2.time = sixDaysAgo;
|
|
|
|
const added = hosts.add(addr2);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(entry.addr.time, sixDaysAgo);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
}
|
|
|
|
// Online - but still not updating (less than 3 hrs)
|
|
{
|
|
// now vs 3 hrs ago (exactly)
|
|
const now = util.now();
|
|
const threeHoursAgo = now - 3 * 60 * 60;
|
|
|
|
const [hosts, entry, addr, src] = getHosts(threeHoursAgo);
|
|
|
|
const addr2 = addr.clone();
|
|
addr2.time = now;
|
|
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(entry.addr.time, threeHoursAgo);
|
|
assert.strictEqual(hosts.needsFlush, false);
|
|
}
|
|
|
|
{
|
|
// now vs 3 hrs and a second ago
|
|
const now = util.now();
|
|
const threeHoursAgo = now - 1 - 3 * 60 * 60;
|
|
|
|
const [hosts, entry, addr, src] = getHosts(threeHoursAgo);
|
|
|
|
const addr2 = addr.clone();
|
|
addr2.time = now;
|
|
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, false);
|
|
assert.strictEqual(entry.addr.time, now);
|
|
assert.strictEqual(hosts.needsFlush, true);
|
|
}
|
|
});
|
|
|
|
it('should evict entry from fresh bucket', () => {
|
|
const hosts = new HostList();
|
|
const bucket = hosts.fresh[0];
|
|
|
|
const src = getRandomNetAddr();
|
|
const entries = [];
|
|
|
|
// Sort them young -> old
|
|
for (let i = 0; i < 10; i++) {
|
|
const entry = new HostEntry(getRandomNetAddr(), src);
|
|
entry.addr.time = util.now() - i;
|
|
entry.refCount = 1;
|
|
bucket.set(entry.key(), entry);
|
|
hosts.map.set(entry.key(), entry);
|
|
entries.push(entry);
|
|
hosts.totalFresh++;
|
|
}
|
|
|
|
{
|
|
const staleEntry = entries[0];
|
|
const expectedEvicted = entries[entries.length - 1];
|
|
|
|
// stales are evicted anyway.
|
|
staleEntry.addr.time = 0;
|
|
|
|
// so we evict 2.
|
|
assert.strictEqual(hosts.isStale(staleEntry), true);
|
|
assert.strictEqual(bucket.has(staleEntry.key()), true);
|
|
assert.strictEqual(bucket.has(expectedEvicted.key()), true);
|
|
assert.strictEqual(hosts.map.has(staleEntry.key()), true);
|
|
hosts.evictFresh(bucket);
|
|
assert.strictEqual(bucket.has(staleEntry.key()), false);
|
|
assert.strictEqual(bucket.has(expectedEvicted.key()), false);
|
|
assert.strictEqual(hosts.map.has(staleEntry.key()), false);
|
|
}
|
|
|
|
{
|
|
// evict older even if it's stale but is in another bucket as well.?
|
|
const staleEntry = entries[1];
|
|
const expectedEvicted = entries[entries.length - 2];
|
|
|
|
staleEntry.attempts = HostList.RETRIES;
|
|
staleEntry.refCount = 2;
|
|
|
|
assert.strictEqual(hosts.isStale(staleEntry), true);
|
|
|
|
assert.strictEqual(bucket.has(staleEntry.key()), true);
|
|
assert.strictEqual(bucket.has(expectedEvicted.key()), true);
|
|
assert.strictEqual(hosts.map.has(staleEntry.key()), true);
|
|
hosts.evictFresh(bucket);
|
|
assert.strictEqual(bucket.has(staleEntry.key()), false);
|
|
assert.strictEqual(bucket.has(expectedEvicted.key()), false);
|
|
assert.strictEqual(hosts.map.has(staleEntry.key()), true);
|
|
}
|
|
|
|
{
|
|
const expectedEvicted = entries[entries.length - 3];
|
|
expectedEvicted.refCount = 2;
|
|
|
|
assert.strictEqual(bucket.has(expectedEvicted.key()), true);
|
|
assert.strictEqual(hosts.map.has(expectedEvicted.key()), true);
|
|
hosts.evictFresh(bucket);
|
|
assert.strictEqual(bucket.has(expectedEvicted.key()), false);
|
|
assert.strictEqual(hosts.map.has(expectedEvicted.key()), true);
|
|
}
|
|
|
|
assert.strictEqual(bucket.size, 5);
|
|
for (let i = entries.length - 4; i > 1; i--) {
|
|
const entry = entries[i];
|
|
assert.strictEqual(bucket.has(entry.key()), true);
|
|
assert.strictEqual(hosts.map.has(entry.key()), true);
|
|
hosts.evictFresh(bucket);
|
|
assert.strictEqual(bucket.has(entry.key()), false);
|
|
assert.strictEqual(hosts.map.has(entry.key()), false);
|
|
}
|
|
|
|
assert.strictEqual(bucket.size, 0);
|
|
hosts.evictFresh(bucket);
|
|
assert.strictEqual(bucket.size, 0);
|
|
});
|
|
});
|
|
|
|
describe('Host manipulation (used/fresh)', function() {
|
|
it('should mark attempt', () => {
|
|
const hosts = new HostList();
|
|
|
|
// if we don't have the entry.
|
|
{
|
|
const addr = getRandomIPv4();
|
|
hosts.markAttempt(addr);
|
|
}
|
|
|
|
const src = getRandomNetAddr();
|
|
const addr = getRandomNetAddr();
|
|
|
|
hosts.add(addr, src);
|
|
|
|
const entry = hosts.map.get(addr.hostname);
|
|
assert.strictEqual(entry.attempts, 0);
|
|
assert.strictEqual(entry.lastAttempt, 0);
|
|
|
|
hosts.markAttempt(addr.hostname);
|
|
assert.strictEqual(entry.attempts, 1);
|
|
assert(entry.lastAttempt > util.now() - 10);
|
|
});
|
|
|
|
it('should mark success', () => {
|
|
const hosts = new HostList();
|
|
|
|
// we don't have entry.
|
|
{
|
|
const addr = getRandomIPv4();
|
|
hosts.markSuccess(addr);
|
|
}
|
|
|
|
// Don't update time, it's recent.
|
|
{
|
|
const src = getRandomNetAddr();
|
|
const addr = getRandomNetAddr();
|
|
const oldTime = util.now() - 10 * 60; // last connection 11 minutes ago.
|
|
addr.time = oldTime;
|
|
|
|
hosts.add(addr, src);
|
|
hosts.markSuccess(addr.hostname);
|
|
|
|
const entry = hosts.map.get(addr.hostname);
|
|
assert.strictEqual(entry.addr.time, oldTime);
|
|
}
|
|
|
|
// we update time.
|
|
const src = getRandomNetAddr();
|
|
const addr = getRandomNetAddr();
|
|
const oldTime = util.now() - 21 * 60; // last connection 21 minutes ago.
|
|
addr.time = oldTime;
|
|
|
|
hosts.add(addr, src);
|
|
hosts.markSuccess(addr.hostname);
|
|
|
|
const entry = hosts.map.get(addr.hostname);
|
|
assert(entry.addr.time > oldTime);
|
|
});
|
|
|
|
it('should remove host', () => {
|
|
const hosts = new HostList();
|
|
|
|
const src = getRandomNetAddr();
|
|
const addrs = [
|
|
getRandomNetAddr(),
|
|
getRandomNetAddr(),
|
|
getRandomNetAddr(),
|
|
getRandomNetAddr()
|
|
];
|
|
|
|
const used = addrs.slice(2);
|
|
|
|
for (const addr of addrs)
|
|
hosts.add(addr, src);
|
|
|
|
for (const addr of used)
|
|
hosts.markAck(addr.hostname, 0);
|
|
|
|
assert.strictEqual(hosts.map.size, addrs.length);
|
|
assert.strictEqual(hosts.totalUsed, 2);
|
|
assert.strictEqual(hosts.totalFresh, 2);
|
|
const fresh = getFreshEntries(hosts);
|
|
assert.strictEqual(fresh.length, 2);
|
|
|
|
assert.strictEqual(hosts.remove(getRandomIPv6()), null);
|
|
for (const addr of addrs.reverse())
|
|
assert.strictEqual(hosts.remove(addr.hostname), addr);
|
|
assert.strictEqual(hosts.totalUsed, 0);
|
|
assert.strictEqual(hosts.totalFresh, 0);
|
|
});
|
|
|
|
it('should mark ack', () => {
|
|
// we don't have the entry
|
|
{
|
|
const hosts = new HostList();
|
|
const addr = getRandomIPv4();
|
|
hosts.markAck(addr);
|
|
}
|
|
|
|
// Should update services, lastSuccess, lastAttempt and attempts
|
|
// even if it's already in the used.
|
|
{
|
|
const hosts = new HostList();
|
|
const naddr = getRandomNetAddr();
|
|
const nsrc = getRandomNetAddr();
|
|
|
|
naddr.services = 0x01;
|
|
hosts.add(naddr, nsrc);
|
|
|
|
const entry = hosts.map.get(naddr.hostname);
|
|
const oldLastAttempt = util.now() - 1000;
|
|
const oldLastSuccess = util.now() - 1000;
|
|
const oldAttempts = 2;
|
|
|
|
entry.lastAttempt = oldLastAttempt;
|
|
entry.lastSuccess = oldLastSuccess;
|
|
entry.attempts = oldAttempts;
|
|
entry.used = true;
|
|
|
|
hosts.markAck(naddr.hostname, 0x02);
|
|
|
|
assert(entry.lastSuccess > oldLastSuccess);
|
|
assert(entry.lastAttempt > oldLastAttempt);
|
|
assert.strictEqual(entry.attempts, 0);
|
|
assert.strictEqual(entry.addr.services, 0x01 | 0x02);
|
|
}
|
|
|
|
// Should remove from fresh
|
|
{
|
|
const hosts = new HostList();
|
|
|
|
// make sure we have all 8 refs.
|
|
let index = 0;
|
|
hosts.random = () => 0;
|
|
|
|
// make sure we always get different bucket.
|
|
hosts.freshBucket = function () {
|
|
return this.fresh[index++];
|
|
};
|
|
|
|
const addr = getRandomNetAddr();
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
const src = getRandomNetAddr();
|
|
const addr2 = addr.clone();
|
|
addr2.time = addr.time + i + 1;
|
|
|
|
const added = hosts.add(addr2, src);
|
|
assert.strictEqual(added, true);
|
|
}
|
|
|
|
assert.strictEqual(hosts.totalFresh, 1);
|
|
assert.strictEqual(hosts.totalUsed, 0);
|
|
assert.strictEqual(getFreshEntries(hosts).length, 8);
|
|
const entry = hosts.map.get(addr.hostname);
|
|
assert.strictEqual(entry.refCount, 8);
|
|
|
|
hosts.markAck(addr.hostname);
|
|
|
|
assert.strictEqual(getFreshEntries(hosts).length, 0);
|
|
assert.strictEqual(entry.refCount, 0);
|
|
assert.strictEqual(hosts.totalFresh, 0);
|
|
assert.strictEqual(hosts.totalUsed, 1);
|
|
assert.strictEqual(entry.used, true);
|
|
}
|
|
|
|
// evict used
|
|
{
|
|
const hosts = new HostList();
|
|
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
|
|
hosts.add(addr, src);
|
|
const entry = hosts.map.get(addr.hostname);
|
|
const bucket = hosts.usedBucket(entry);
|
|
|
|
assert.strictEqual(hosts.totalFresh, 1);
|
|
assert.strictEqual(hosts.totalUsed, 0);
|
|
|
|
// add 64 entries to the bucket.
|
|
const entries = [];
|
|
for (let i = 0; i < hosts.maxEntries; i++) {
|
|
const addr = getRandomNetAddr();
|
|
const src = getRandomNetAddr();
|
|
addr.time = util.now() - (i);
|
|
|
|
const entry = new HostEntry(addr, src);
|
|
entry.used = true;
|
|
bucket.push(entry);
|
|
entries.push(entry);
|
|
}
|
|
|
|
const expectedEvicted = entries[0];
|
|
hosts.markAck(addr.hostname);
|
|
assert.strictEqual(bucket.tail, entry);
|
|
assert.strictEqual(expectedEvicted.used, true);
|
|
}
|
|
});
|
|
|
|
it('should check if entry is stale', () => {
|
|
const hosts = new HostList();
|
|
|
|
const src = getRandomNetAddr();
|
|
const addrs = [];
|
|
const entries = [];
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const addr = getRandomNetAddr();
|
|
hosts.add(addr, src);
|
|
const entry = hosts.map.get(addr.hostname);
|
|
entries.push(entry);
|
|
addrs.push(addr);
|
|
}
|
|
|
|
const A_DAY = 24 * 60 * 60;
|
|
|
|
// address from the future?
|
|
entries[0].addr.time = util.now() + 30 * 60;
|
|
|
|
entries[1].addr.time = 0;
|
|
|
|
// too old
|
|
entries[2].addr.time = util.now() - HostList.HORIZON_DAYS * A_DAY - 1;
|
|
|
|
// many attempts, no success
|
|
entries[3].attempts = HostList.RETRIES;
|
|
|
|
// last success got old.
|
|
// and we failed max times.
|
|
entries[4].lastSuccess = util.now() - HostList.MIN_FAIL_DAYS * A_DAY - 1;
|
|
entries[4].attempts = HostList.MAX_FAILURES;
|
|
|
|
// last attempt in last minute
|
|
entries[5].lastAttempt = util.now();
|
|
|
|
entries[6].lastSuccess = entries[5].lastSuccess;
|
|
entries[7].lastSuccess = entries[5].lastSuccess + A_DAY;
|
|
|
|
for (let i = 0; i < entries.length; i++) {
|
|
const entry = entries[i];
|
|
|
|
if (i < 5)
|
|
assert.strictEqual(hosts.isStale(entry), true);
|
|
else
|
|
assert.strictEqual(hosts.isStale(entry), false);
|
|
}
|
|
});
|
|
|
|
it('should return array of entries', () => {
|
|
const hosts = new HostList();
|
|
|
|
const src = getRandomNetAddr();
|
|
const addrs = [];
|
|
const entries = [];
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
const addr = getRandomNetAddr();
|
|
addrs.push(addr);
|
|
hosts.add(addr, src);
|
|
entries.push(hosts.map.get(addr.hostname));
|
|
}
|
|
|
|
// have first 2 entries stale.
|
|
entries[0].addr.time = 0;
|
|
entries[1].addr.time = util.now() + 20 * 60;
|
|
|
|
const arr = hosts.toArray();
|
|
const set = new Set(arr);
|
|
|
|
assert.strictEqual(arr.length, entries.length - 2);
|
|
assert.strictEqual(set.size, entries.length - 2);
|
|
|
|
for (let i = 0; i < 2; i++)
|
|
assert.strictEqual(set.has(addrs[i]), false);
|
|
|
|
for (let i = 2; i < entries.length; i++)
|
|
assert.strictEqual(set.has(addrs[i]), true);
|
|
});
|
|
|
|
it('should get host', () => {
|
|
{
|
|
// empty
|
|
const hosts = new HostList();
|
|
const host = hosts.getHost();
|
|
assert.strictEqual(host, null);
|
|
}
|
|
|
|
{
|
|
// fresh buckets
|
|
const hosts = new HostList();
|
|
|
|
const freshEntries = [];
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
const entry = new HostEntry(getRandomNetAddr(), getRandomNetAddr());
|
|
freshEntries.push(entry);
|
|
add2bucket(hosts, 0, entry, true);
|
|
}
|
|
|
|
const found = hosts.getHost();
|
|
assert.strictEqual(new Set(freshEntries).has(found), true);
|
|
}
|
|
|
|
{
|
|
// used bucket - this is random.
|
|
const hosts = new HostList();
|
|
// put 10 entries in the used.
|
|
const usedEntries = [];
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
const entry = new HostEntry(getRandomNetAddr(), getRandomNetAddr());
|
|
usedEntries.push(entry);
|
|
add2bucket(hosts, 0, usedEntries[i], false);
|
|
}
|
|
|
|
const foundEntry = hosts.getHost();
|
|
assert.strictEqual(new Set(usedEntries).has(foundEntry), true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('populate', function() {
|
|
const DNS_SEED = IP.fromHost('example.com');
|
|
|
|
let hosts;
|
|
beforeEach(() => {
|
|
hosts = new HostList();
|
|
});
|
|
|
|
it('should populate', async () => {
|
|
let err;
|
|
try {
|
|
// only DNS name is a valid seed.
|
|
await hosts.populate(getRandomIPv4());
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.strictEqual(err.message, 'Resolved host passed.');
|
|
|
|
hosts.resolve = () => {
|
|
throw new Error('pop error');
|
|
};
|
|
|
|
const failedAddrs = await hosts.populate(DNS_SEED);
|
|
assert.strictEqual(failedAddrs.length, 0);
|
|
|
|
const addrs = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
hosts.resolve = () => addrs;
|
|
const resAddrs = await hosts.populate(DNS_SEED);
|
|
assert.strictEqual(resAddrs.length, addrs.length);
|
|
|
|
for (const [i, resAddr] of resAddrs.entries())
|
|
assert.strictEqual(resAddr.host, addrs[i]);
|
|
});
|
|
|
|
it('should populate seed', async () => {
|
|
const addrs = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
hosts.resolve = () => addrs;
|
|
|
|
await hosts.populateSeed(DNS_SEED);
|
|
|
|
assert.strictEqual(hosts.map.size, addrs.length);
|
|
for (const addr of addrs)
|
|
assert(hosts.map.has(`${addr}:${mainnet.port}`));
|
|
|
|
const fresh = getFreshEntries(hosts);
|
|
assert.strictEqual(fresh.length, addrs.length);
|
|
});
|
|
|
|
it('should populate node', async () => {
|
|
// Populate empty.
|
|
hosts.resolve = () => [];
|
|
await hosts.populateNode(DNS_SEED);
|
|
|
|
assert.strictEqual(hosts.nodes.length, 0);
|
|
assert.strictEqual(hosts.map.size, 0);
|
|
|
|
const addrs = [
|
|
getRandomIPv4(),
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
];
|
|
|
|
// we just take first resolved IP as a Node.
|
|
hosts.resolve = () => addrs;
|
|
await hosts.populateNode(DNS_SEED);
|
|
assert.strictEqual(hosts.nodes.length, 1);
|
|
assert.strictEqual(hosts.map.size, 1);
|
|
|
|
assert.strictEqual(hosts.nodes[0].host, addrs[0]);
|
|
assert(hosts.map.has(`${addrs[0]}:${mainnet.port}`));
|
|
});
|
|
|
|
it('should discover seeds', async () => {
|
|
const seeds = {
|
|
'example.com': [
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
],
|
|
'example.org': [
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
]
|
|
};
|
|
|
|
hosts.resolve = host => seeds[host];
|
|
|
|
for (const seed of Object.keys(seeds))
|
|
hosts.addSeed(seed);
|
|
|
|
await hosts.discoverSeeds();
|
|
|
|
assert.strictEqual(hosts.map.size, 4);
|
|
|
|
for (const ips of Object.values(seeds)) {
|
|
for (const ip of ips)
|
|
assert(hosts.map.has(`${ip}:${mainnet.port}`));
|
|
}
|
|
});
|
|
|
|
it('should discover nodes', async () => {
|
|
const seeds = {
|
|
'example.com': [
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
],
|
|
'example.org': [
|
|
getRandomIPv4(),
|
|
getRandomIPv4()
|
|
]
|
|
};
|
|
|
|
hosts.resolve = host => seeds[host];
|
|
|
|
for (const seed of Object.keys(seeds))
|
|
hosts.addNode(seed);
|
|
|
|
await hosts.discoverNodes();
|
|
|
|
assert.strictEqual(hosts.map.size, 2);
|
|
|
|
for (const [i, ips] of Object.values(seeds).entries()) {
|
|
assert(hosts.map.has(`${ips[0]}:${mainnet.port}`));
|
|
assert(hosts.nodes[i].host, ips[0]);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('File', function() {
|
|
let testdir;
|
|
|
|
beforeEach(async () => {
|
|
testdir = common.testdir('hostlist');
|
|
|
|
await fs.mkdirp(testdir);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rimraf(testdir);
|
|
testdir = null;
|
|
});
|
|
|
|
// Generate 1 entry per bucket from 0 to nFresh or nUsed.
|
|
const genEntries = (hosts, nFresh, nUsed) => {
|
|
const fresh = [];
|
|
const used = [];
|
|
|
|
assert(nFresh < hosts.maxFreshBuckets);
|
|
assert(nUsed < hosts.maxUsedBuckets);
|
|
|
|
for (let i = 0; i < nFresh; i++) {
|
|
const entry = new HostEntry(getRandomNetAddr(), getRandomNetAddr());
|
|
add2bucket(hosts, i, entry, true);
|
|
fresh.push(entry);
|
|
}
|
|
|
|
for (let i = 0; i < nUsed; i++) {
|
|
const entry = new HostEntry(getRandomNetAddr(), getRandomNetAddr());
|
|
add2bucket(hosts, i, entry, false);
|
|
used.push(entry);
|
|
}
|
|
|
|
return [fresh, used];
|
|
};
|
|
|
|
it('should reserialize JSON', () => {
|
|
const hosts = new HostList({
|
|
// network: regtest
|
|
});
|
|
|
|
genEntries(hosts, 10, 10);
|
|
|
|
const json = hosts.toJSON();
|
|
const hosts2 = HostList.fromJSON({
|
|
// network: regtest
|
|
}, json);
|
|
|
|
assert.deepStrictEqual(hosts2.fresh, hosts.fresh);
|
|
assert.deepStrictEqual(hosts2.used, hosts.used);
|
|
assert.deepStrictEqual(hosts2.map, hosts.map);
|
|
assert.strictEqual(hosts2.map.size, 20);
|
|
assert.strictEqual(getFreshEntries(hosts2).length, 10);
|
|
assert.strictEqual(getUsedEntries(hosts2).length, 10);
|
|
});
|
|
|
|
it('should open empty', async () => {
|
|
const hosts = new HostList({
|
|
network: regtest,
|
|
prefix: testdir
|
|
});
|
|
|
|
await hosts.open();
|
|
assert.strictEqual(hosts.map.size, 0);
|
|
assert.strictEqual(getFreshEntries(hosts).length, 0);
|
|
assert.strictEqual(getUsedEntries(hosts).length, 0);
|
|
await hosts.close();
|
|
|
|
// it does not need flushing.
|
|
assert(!await fs.exists(path.join(testdir, 'hosts.json')));
|
|
});
|
|
|
|
it('should create hosts.json', async () => {
|
|
const hosts = new HostList({
|
|
network: regtest,
|
|
prefix: testdir,
|
|
memory: false
|
|
});
|
|
|
|
await hosts.open();
|
|
|
|
genEntries(hosts, 10, 10);
|
|
hosts.needsFlush = true;
|
|
assert.strictEqual(hosts.map.size, 20);
|
|
assert.strictEqual(getFreshEntries(hosts).length, 10);
|
|
assert.strictEqual(getUsedEntries(hosts).length, 10);
|
|
await hosts.close();
|
|
|
|
assert(await fs.exists(path.join(testdir, 'hosts.json')));
|
|
|
|
const hosts2 = new HostList({
|
|
network: regtest,
|
|
prefix: testdir,
|
|
memory: false
|
|
});
|
|
|
|
await hosts2.open();
|
|
assert.strictEqual(hosts2.map.size, 20);
|
|
assert.strictEqual(getFreshEntries(hosts2).length, 10);
|
|
assert.strictEqual(getUsedEntries(hosts2).length, 10);
|
|
await hosts2.close();
|
|
});
|
|
});
|
|
});
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
function getRandomIPv4() {
|
|
const prefix = Buffer.from('00000000000000000000ffff', 'hex');
|
|
const number = random.randomBytes(4);
|
|
const ipv4 = Buffer.concat([prefix, number]);
|
|
|
|
if (IP.getNetwork(ipv4) === IP.networks.INET4)
|
|
return IP.toString(ipv4);
|
|
|
|
return getRandomIPv4();
|
|
}
|
|
|
|
function getRandomTEREDO() {
|
|
const prefix = Buffer.from('20010000', 'hex');
|
|
const number = random.randomBytes(12);
|
|
const raw = Buffer.concat([prefix, number]);
|
|
|
|
if (IP.getNetwork(raw) === IP.networks.TEREDO)
|
|
return IP.toString(raw);
|
|
|
|
return getRandomTEREDO();
|
|
}
|
|
|
|
function getRandomIPv6() {
|
|
const raw = random.randomBytes(16);
|
|
|
|
if (IP.isRFC3964(raw) || IP.isRFC6052(raw) || IP.isRFC6145(raw))
|
|
return getRandomIPv6();
|
|
|
|
if (IP.getNetwork(raw) === IP.networks.INET6)
|
|
return IP.toString(raw);
|
|
|
|
return getRandomIPv6();
|
|
}
|
|
|
|
function getRandomOnion() {
|
|
const prefix = Buffer.from('fd87d87eeb43', 'hex');
|
|
const number = random.randomBytes(10);
|
|
const raw = Buffer.concat([prefix, number]);
|
|
|
|
if (IP.getNetwork(raw) === IP.networks.ONION)
|
|
return IP.toString(raw);
|
|
|
|
return getRandomOnion();
|
|
}
|