Merge PR #692 from 'nodech/net-updates'

This commit is contained in:
Nodari Chkuaselidze 2022-04-07 11:35:09 +04:00
commit 6213460846
No known key found for this signature in database
GPG key ID: B018A7BB437D1F05
11 changed files with 3413 additions and 154 deletions

View file

@ -23,6 +23,42 @@ const NetAddress = require('./netaddress');
const common = require('./common');
const seeds = require('./seeds');
/**
* Stochastic address manager based on bitcoin addrman.
*
* Design goals:
* * Keep the address tables in-memory, and asynchronously dump the entire
* table to hosts.json.
* * Make sure no (localized) attacker can fill the entire table with his
* nodes/addresses.
*
* To that end:
* * Addresses are organized into buckets that can each store up
* to 64 entries (maxEntries).
* * Addresses to which our node has not successfully connected go into
* 1024 "fresh" buckets (maxFreshBuckets).
* * Based on the address range of the source of information
* 64 buckets are selected at random.
* * The actual bucket is chosen from one of these, based on the range in
* which the address itself is located.
* * The position in the bucket is chosen based on the full address.
* * One single address can occur in up to 8 different buckets to increase
* selection chances for addresses that are seen frequently. The chance
* for increasing this multiplicity decreases exponentially.
* * When adding a new address to an occupied position of a bucket, it
* will not replace the existing entry unless that address is also stored
* in another bucket or it doesn't meet one of several quality criteria
* (see isStale for exact criteria).
* * Addresses of nodes that are known to be accessible go into 256 "tried"
* buckets.
* * Each address range selects at random 8 of these buckets.
* * The actual bucket is chosen from one of these, based on the full
* address.
* * Bucket selection is based on cryptographic hashing,
* using a randomly-generated 256-bit key, which should not be observable
* by adversaries (key).
*/
/**
* Host List
* @alias module:net.HostList
@ -42,6 +78,7 @@ class HostList {
this.address = this.options.address;
this.brontide = this.options.brontide;
this.resolve = this.options.resolve;
this.random = this.options.random;
this.key = rng.randomBytes(32);
this.hash = new Hash256();
@ -381,7 +418,7 @@ class HostList {
buckets = this.fresh;
if (this.totalUsed > 0) {
if (this.totalFresh === 0 || random(2) === 0)
if (this.totalFresh === 0 || this.random(2) === 0)
buckets = this.used;
}
@ -393,13 +430,13 @@ class HostList {
let factor = 1;
for (;;) {
const i = random(buckets.length);
const i = this.random(buckets.length);
const bucket = buckets[i];
if (bucket.size === 0)
continue;
let index = random(bucket.size);
let index = this.random(bucket.size);
let entry;
if (buckets === this.used) {
@ -414,7 +451,7 @@ class HostList {
}
}
const num = random(1 << 30);
const num = this.random(1 << 30);
if (num < factor * entry.chance(now) * (1 << 30))
return entry;
@ -427,17 +464,20 @@ class HostList {
* Get fresh bucket for host.
* @private
* @param {HostEntry} entry
* @param {NetAddress?} src
* @returns {Map}
*/
freshBucket(entry) {
freshBucket(entry, src) {
const addr = entry.addr;
const src = entry.src;
if (!src)
src = entry.src;
this.hash.init();
this.hash.update(this.key);
this.hash.update(groupKey(addr.raw));
this.hash.update(groupKey(src.raw));
this.hash.update(addr.getGroup());
this.hash.update(src.getGroup());
const hash1 = this.hash.final();
const hash32 = bio.readU32(hash1, 0) % 64;
@ -446,7 +486,7 @@ class HostList {
this.hash.init();
this.hash.update(this.key);
this.hash.update(groupKey(src.raw));
this.hash.update(src.getGroup());
this.hash.update(this.hashbuf);
const hash2 = this.hash.final();
@ -481,7 +521,7 @@ class HostList {
this.hash.init();
this.hash.update(this.key);
this.hash.update(groupKey(addr.raw));
this.hash.update(addr.getGroup());
this.hash.update(this.hashbuf);
const hash2 = this.hash.final();
@ -552,7 +592,7 @@ class HostList {
for (let i = 0; i < entry.refCount; i++)
factor *= 2;
if (random(factor) !== 0)
if (this.random(factor) !== 0)
return false;
} else {
if (!src)
@ -563,7 +603,7 @@ class HostList {
this.totalFresh += 1;
}
const bucket = this.freshBucket(entry);
const bucket = this.freshBucket(entry, src);
if (bucket.has(entry.key()))
return false;
@ -828,7 +868,7 @@ class HostList {
items.push(entry);
for (let i = 0; i < items.length && out.length < 2500; i++) {
const j = random(items.length - i);
const j = this.random(items.length - i);
[items[i], items[i + j]] = [items[i + j], items[i]];
@ -1409,6 +1449,14 @@ HostList.scores = {
/**
* Host Entry
* @alias module:net.HostEntry
* @property {NetAddress} addr - host address.
* @property {NetAddress} src - the first address we discovered this entry
* from.
* @property {Boolean} used - is it in the used set.
* @property {Number} refCount - Reference count in new buckets.
* @property {Number} attempts - connection attempts since last successful one.
* @property {Number} lastSuccess - last success timestamp.
* @property {Number} lastAttempt - last attempt timestamp.
*/
class HostEntry {
@ -1630,6 +1678,7 @@ class HostListOptions {
this.onion = false;
this.brontideOnly = false;
this.banTime = common.BAN_TIME;
this.random = random;
this.address = new NetAddress();
this.address.services = this.services;
@ -1762,6 +1811,11 @@ class HostListOptions {
this.flushInterval = options.flushInterval;
}
if (options.random != null) {
assert(typeof options.random === 'function');
this.random = options.random;;
}
this.address.time = this.network.now();
this.address.services = this.services;
@ -1781,65 +1835,11 @@ function random(max) {
return Math.floor(Math.random() * max);
}
function groupKey(raw) {
// See: https://github.com/bitcoin/bitcoin/blob/e258ce7/src/netaddress.cpp#L413
// Todo: Use IP->ASN mapping, see:
// https://github.com/bitcoin/bitcoin/blob/adea5e1/src/addrman.h#L274
let type = 6; // NET_IPV6
let start = 0;
let bits = 16;
let i = 0;
if (IP.isLocal(raw)) {
type = 255; // NET_LOCAL
bits = 0;
} else if (!IP.isRoutable(raw)) {
type = 0; // NET_UNROUTABLE
bits = 0;
} else if (IP.isIPv4(raw) || IP.isRFC6145(raw) || IP.isRFC6052(raw)) {
type = 4; // NET_IPV4
start = 12;
} else if (IP.isRFC3964(raw)) {
type = 4; // NET_IPV4
start = 2;
} else if (IP.isRFC4380(raw)) {
const buf = Buffer.alloc(3);
buf[0] = 4; // NET_IPV4
buf[1] = raw[12] ^ 0xff;
buf[2] = raw[13] ^ 0xff;
return buf;
} else if (IP.isOnion(raw)) {
type = 8; // NET_ONION
start = 6;
bits = 4;
} else if (raw[0] === 0x20
&& raw[1] === 0x01
&& raw[2] === 0x04
&& raw[3] === 0x70) {
bits = 36;
} else {
bits = 32;
}
const out = Buffer.alloc(1 + ((bits + 7) >>> 3));
out[i++] = type;
while (bits >= 8) {
out[i++] = raw[start++];
bits -= 8;
}
if (bits > 0)
out[i++] = raw[start] | ((1 << (8 - bits)) - 1);
assert(i === out.length);
return out;
}
/*
* Expose
*/
module.exports = HostList;
exports = HostList;
exports.HostEntry = HostEntry;
module.exports = exports;

View file

@ -49,7 +49,7 @@ class NetAddress extends bio.Struct {
this.services = 0;
this.time = 0;
this.hostname = '0.0.0.0:0';
this.raw = IP.ZERO_IP;
this.raw = IP.ZERO_IPV4;
this.key = ZERO_KEY;
if (options)
@ -63,26 +63,32 @@ class NetAddress extends bio.Struct {
*/
fromOptions(options) {
assert(typeof options.host === 'string');
assert(typeof options.port === 'number');
assert(typeof options.host === 'string',
'NetAddress requires host string.');
assert(typeof options.port === 'number',
'NetAddress requires port number.');
assert(options.port >= 0 && options.port <= 0xffff,
'port number is incorrect.');
this.raw = IP.toBuffer(options.host);
this.host = IP.toString(this.raw);
this.port = options.port;
if (options.services) {
assert(typeof options.services === 'number');
assert(typeof options.services === 'number',
'services must be a number.');
this.services = options.services;
}
if (options.time) {
assert(typeof options.time === 'number');
assert(typeof options.time === 'number',
'time must be a number.');
this.time = options.time;
}
if (options.key) {
assert(Buffer.isBuffer(options.key));
assert(options.key.length === 33);
assert(Buffer.isBuffer(options.key), 'key must be a buffer.');
assert(options.key.length === 33, 'key length must be 33.');
this.key = options.key;
}
@ -119,6 +125,42 @@ class NetAddress extends bio.Struct {
return IP.isIPv6(this.raw);
}
/**
* Test whether the address is RFC3964.
* @returns {Boolean}
*/
isRFC3964() {
return IP.isRFC3964(this.raw);
}
/**
* Test whether the address is RFC4380.
* @returns {Boolean}
*/
isRFC4380() {
return IP.isRFC4380(this.raw);
}
/**
* Test whether the address is RFC6052.
* @returns {Boolean}
*/
isRFC6052() {
return IP.isRFC6052(this.raw);
}
/**
* Test whether the address is RFC6145.
* @returns {Boolean}
*/
isRFC6145() {
return IP.isRFC6145(this.raw);
}
/**
* Test whether the host is null.
* @returns {Boolean}
@ -206,12 +248,21 @@ class NetAddress extends bio.Struct {
return IP.getReachability(this.raw, dest.raw);
}
/**
* Get the canonical identifier of our network group
* @returns {Buffer}
*/
getGroup() {
return groupKey(this);
}
/**
* Set null host.
*/
setNull() {
this.raw = IP.ZERO_IP;
this.raw = IP.ZERO_IPV4;
this.host = '0.0.0.0';
this.key = ZERO_KEY;
this.hostname = IP.toHostname(this.host, this.port, this.key);
@ -487,6 +538,74 @@ NetAddress.DEFAULT_SERVICES = 0
| common.services.NETWORK
| common.services.BLOOM;
/*
* Helpers
*/
/**
* @param {NetAddress} addr
* @returns {Number}
*/
function groupKey(addr) {
const raw = addr.raw;
// See: https://github.com/bitcoin/bitcoin/blob/e258ce7/src/netaddress.cpp#L413
// Todo: Use IP->ASN mapping, see:
// https://github.com/bitcoin/bitcoin/blob/adea5e1/src/addrman.h#L274
let type = IP.networks.INET6; // NET_IPV6
let start = 0;
let bits = 16;
let i = 0;
if (addr.isLocal()) {
type = 255; // NET_LOCAL
bits = 0;
} else if (!addr.isRoutable()) {
type = IP.networks.NONE; // NET_UNROUTABLE
bits = 0;
} else if (addr.isIPv4() || addr.isRFC6145() || addr.isRFC6052()) {
type = IP.networks.INET4; // NET_IPV4
start = 12;
} else if (addr.isRFC3964()) {
type = IP.networks.INET4; // NET_IPV4
start = 2;
} else if (addr.isRFC4380()) {
const buf = Buffer.alloc(3);
buf[0] = IP.networks.INET4; // NET_IPV4
buf[1] = raw[12] ^ 0xff;
buf[2] = raw[13] ^ 0xff;
return buf;
} else if (addr.isOnion()) {
type = IP.networks.ONION; // NET_ONION
start = 6;
bits = 4;
} else if (raw[0] === 0x20
&& raw[1] === 0x01
&& raw[2] === 0x04
&& raw[3] === 0x70) {
bits = 36;
} else {
bits = 32;
}
const out = Buffer.alloc(1 + ((bits + 7) >>> 3));
out[i++] = type;
while (bits >= 8) {
out[i++] = raw[start++];
bits -= 8;
}
if (bits > 0)
out[i++] = raw[start] | ((1 << (8 - bits)) - 1);
assert(i === out.length);
return out;
}
/*
* Expose
*/

View file

@ -51,7 +51,7 @@ class Peer extends EventEmitter {
constructor(options) {
super();
this.options = options;
this.options = new PeerOptions(options);
this.network = this.options.network;
this.logger = this.options.logger.context('peer');
this.locker = new Lock();
@ -164,16 +164,6 @@ class Peer extends EventEmitter {
return peer;
}
/**
* Create a peer from options.
* @param {Object} options
* @returns {Peer}
*/
static fromOptions(options) {
return new this(new PeerOptions(options));
}
/**
* Begin peer initialization.
* @private
@ -2161,7 +2151,7 @@ class PeerOptions {
this.compact = false;
this.headers = false;
this.banScore = common.BAN_SCORE;
this.proofPRS = 100;
this.maxProofRPS = 100;
this.getHeight = PeerOptions.getHeight;
this.isFull = PeerOptions.isFull;

View file

@ -10,7 +10,6 @@ const assert = require('bsert');
const EventEmitter = require('events');
const {Lock} = require('bmutex');
const IP = require('binet');
const dns = require('bdns');
const tcp = require('btcp');
const UPNP = require('bupnp');
const socks = require('bsocks');
@ -85,6 +84,7 @@ class Pool extends EventEmitter {
this.pendingFilter = null;
this.refillTimer = null;
this.discoverTimer = null;
this.connectedGroups = new BufferSet();
this.checkpoints = false;
this.headerChain = new List();
@ -342,7 +342,6 @@ class Pool extends EventEmitter {
await this.hosts.open();
await this.discoverGateway();
await this.discoverExternal();
await this.discoverSeeds();
await this.listen();
@ -595,52 +594,6 @@ class Pool extends EventEmitter {
}
}
/**
* Attempt to discover external IP via DNS.
* @returns {Promise}
*/
async discoverExternal() {
const port = this.options.publicPort;
// Pointless if we're not listening.
if (!this.options.listen)
return;
// Never hit a DNS server if
// we're using an outbound proxy.
if (this.options.proxy)
return;
// Try not to hit this if we can avoid it.
if (this.hosts.local.size > 0)
return;
let host4 = null;
try {
host4 = await dns.getIPv4(2000);
} catch (e) {
this.logger.debug('Could not find external IPv4 (dns).');
this.logger.debug(e);
}
if (host4 && this.hosts.addLocal(host4, port, scores.DNS))
this.logger.info('External IPv4 found (dns): %s.', host4);
let host6 = null;
try {
host6 = await dns.getIPv6(2000);
} catch (e) {
this.logger.debug('Could not find external IPv6 (dns).');
this.logger.debug(e);
}
if (host6 && this.hosts.addLocal(host6, port, scores.DNS))
this.logger.info('External IPv6 found (dns): %s.', host6);
}
/**
* Handle incoming connection.
* @private
@ -710,6 +663,7 @@ class Pool extends EventEmitter {
this.logger.info('Adding loader peer (%s).', peer.hostname());
this.peers.add(peer);
this.connectedGroups.add(addr.getGroup());
this.setLoader(peer);
}
@ -1717,8 +1671,6 @@ class Pool extends EventEmitter {
}
}
}
this.fillOutbound();
}
/**
@ -3395,7 +3347,23 @@ class Pool extends EventEmitter {
const services = this.options.getRequiredServices();
const now = this.network.now();
for (let i = 0; i < 100; i++) {
// Calculate maximum number of hosts we can get.
let max = this.hosts.totalFresh + this.hosts.totalUsed;
// We don't want to loop a lot here as it's expensive on CPU.
// If this gets high, such as 100, it could cause a local DoS
// for incoming RPC calls.
if (max > 10)
max = 10;
// Work out a percentage based hit rate outside of the
// loop to save CPU.
// Subtract 1 because we're zero based.
const pc1 = max / 100;
const pc30 = (pc1 * 30) - 1;
const pc50 = (pc1 * 50) - 1;
for (let i = 0; i < max; i++) {
const entry = this.hosts.getHost();
if (!entry)
@ -3424,10 +3392,14 @@ class Pool extends EventEmitter {
if (this.options.brontideOnly && !addr.hasKey())
continue;
if (i < 30 && now - entry.lastAttempt < 600)
// Don't connect to outbound peers in the same group.
if (this.connectedGroups.has(addr.getGroup()))
continue;
if (i < 50 && addr.port !== this.network.port)
if (i < pc30 && now - entry.lastAttempt < 600)
continue;
if (i < pc50 && addr.port !== this.network.port)
continue;
return entry.addr;
@ -3462,6 +3434,7 @@ class Pool extends EventEmitter {
const peer = this.createOutbound(addr);
this.peers.add(peer);
this.connectedGroups.add(addr.getGroup());
this.emit('peer', peer);
}
@ -3516,6 +3489,9 @@ class Pool extends EventEmitter {
removePeer(peer) {
this.peers.remove(peer);
if (peer.outbound)
this.connectedGroups.delete(peer.address.getGroup());
for (const hash of peer.blockMap.keys())
this.resolveBlock(peer, hash);
@ -4378,6 +4354,11 @@ class PoolOptions {
this.createSocket = this._createSocket.bind(this);
this.createServer = tcp.createServer;
this.resolve = this._resolve.bind(this);
this.createNonce = this._createNonce.bind(this);
this.hasNonce = this._hasNonce.bind(this);
this.getHeight = this._getHeight.bind(this);
this.isFull = this._isFull.bind(this);
this.getRate = this._getRate.bind(this);
this.proxy = null;
this.onion = false;
this.brontideOnly = false;
@ -4670,7 +4651,7 @@ class PoolOptions {
* @returns {Number}
*/
getHeight() {
_getHeight() {
return this.chain.height;
}
@ -4680,7 +4661,7 @@ class PoolOptions {
* @returns {Boolean}
*/
isFull() {
_isFull() {
return this.chain.synced;
}
@ -4711,7 +4692,7 @@ class PoolOptions {
* @returns {Buffer}
*/
createNonce(hostname) {
_createNonce(hostname) {
return this.nonces.alloc(hostname);
}
@ -4722,7 +4703,7 @@ class PoolOptions {
* @returns {Boolean}
*/
hasNonce(nonce) {
_hasNonce(nonce) {
return this.nonces.has(nonce);
}
@ -4733,7 +4714,7 @@ class PoolOptions {
* @returns {Rate}
*/
getRate(hash) {
_getRate(hash) {
if (!this.mempool)
return -1;

479
package-lock.json generated
View file

@ -1,8 +1,479 @@
{
"name": "hsd",
"version": "3.0.1",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "hsd",
"version": "3.0.1",
"license": "MIT",
"dependencies": {
"bcfg": "~0.1.7",
"bcrypto": "~5.4.0",
"bdb": "~1.3.0",
"bdns": "~0.1.5",
"bevent": "~0.1.5",
"bfile": "~0.2.2",
"bfilter": "~1.0.5",
"bheep": "~0.1.5",
"binet": "~0.3.7",
"blgr": "~0.2.0",
"blru": "~0.1.6",
"blst": "~0.1.5",
"bmutex": "~0.1.6",
"bns": "~0.15.0",
"bsert": "~0.0.10",
"bsock": "~0.1.9",
"bsocks": "~0.2.6",
"btcp": "~0.1.5",
"buffer-map": "~0.0.7",
"bufio": "~1.0.7",
"bupnp": "~0.2.6",
"bval": "~0.1.6",
"bweb": "~0.1.10",
"goosig": "~0.10.0",
"hs-client": "~0.0.10",
"n64": "~0.2.10",
"urkel": "~0.7.0"
},
"bin": {
"hs-seeder": "bin/hs-seeder",
"hs-wallet": "bin/hsw",
"hsd": "bin/hsd",
"hsd-cli": "bin/hsd-cli",
"hsd-node": "bin/node",
"hsd-spvnode": "bin/spvnode",
"hsw-cli": "bin/hsw-cli"
},
"devDependencies": {
"bmocha": "^2.1.5"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bcfg": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/bcfg/-/bcfg-0.1.7.tgz",
"integrity": "sha512-+4beq5bXwfmxdcEoHYQsaXawh1qFzjLcRvPe5k5ww/NEWzZTm56Jk8LuPmfeGB7X584jZ8xGq6UgMaZnNDa5Ww==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bcrypto": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/bcrypto/-/bcrypto-5.4.0.tgz",
"integrity": "sha512-KDX2CR29o6ZoqpQndcCxFZAtYA1jDMnXU3jmCfzP44g++Cu7AHHtZN/JbrN/MXAg9SLvtQ8XISG+eVD9zH1+Jg==",
"hasInstallScript": true,
"dependencies": {
"bufio": "~1.0.7",
"loady": "~0.0.5"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bcurl": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/bcurl/-/bcurl-0.1.9.tgz",
"integrity": "sha512-WV9LKCqFPtmGwIOqHexJx3Mm/9H/G5bwSCZxJXq9WRrnVQmd58L+Ltxgp/2QicveDG6AgTfepP6JtNiYWbbeHQ==",
"dependencies": {
"brq": "~0.1.8",
"bsert": "~0.0.10",
"bsock": "~0.1.9"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bdb": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/bdb/-/bdb-1.3.0.tgz",
"integrity": "sha512-oJnWnHOTcnJhazwpEzQvPFtSR1IdHtS3PczuLY3klgZTTtRUbARX7tdphQS8iNUUwEVMfuO93eHDWwTICoeJlg==",
"hasInstallScript": true,
"dependencies": {
"bsert": "~0.0.10",
"loady": "~0.0.5"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/bdns": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/bdns/-/bdns-0.1.5.tgz",
"integrity": "sha512-LNVkfM7ynlAD0CvPvO9cKxW8YXt1KOCRQZlRsGZWeMyymUWVdHQpZudAzH9chaFAz6HiwAnQxwDemCKDPy6Mag==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bevent": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/bevent/-/bevent-0.1.5.tgz",
"integrity": "sha512-hs6T3BjndibrAmPSoKTHmKa3tz/c6Qgjv9iZw+tAoxuP6izfTCkzfltBQrW7SuK5xnY22gv9jCEf51+mRH+Qvg==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bfile": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/bfile/-/bfile-0.2.2.tgz",
"integrity": "sha512-X205SsJ7zFAnjeJ/pBLqDqF10x/4Su3pBy8UdVKw4hdGJk7t5pLoRi+uG4rPaDAClGbrEfT/06PGUbYiMYKzTg==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bfilter": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/bfilter/-/bfilter-1.0.5.tgz",
"integrity": "sha512-GupIidtCvLbKhXnA1sxvrwa+gh95qbjafy7P1U1x/2DHxNabXq4nGW0x3rmgzlJMYlVl+c8fMxoMRIwpKYlgcQ==",
"dependencies": {
"bsert": "~0.0.10",
"bufio": "~1.0.6",
"mrmr": "~0.1.6"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bheep": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/bheep/-/bheep-0.1.5.tgz",
"integrity": "sha512-0KR5Zi8hgJBKL35+aYzndCTtgSGakOMxrYw2uszd5UmXTIfx3+drPGoETlVbQ6arTdAzSoQYA1j35vbaWpQXBg==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/binet": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/binet/-/binet-0.3.7.tgz",
"integrity": "sha512-GF+QD4ajs3GWabaVzso7Kn9aZEbwI0e54FKU2ID8bM/7rIk7BpSJytB1KS7SMpix+fWAi9MAGkOgSFljl0aaKg==",
"dependencies": {
"bs32": "~0.1.5",
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/blgr": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/blgr/-/blgr-0.2.0.tgz",
"integrity": "sha512-2jZdqajYCGD5rwGdOooQpxgjKsiAAV2g8LapwSnbTjAYTZAqmqBAS+GsVGFi+/y7t1Pspidv/5HsWBbJrsEuFw==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/blru": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/blru/-/blru-0.1.6.tgz",
"integrity": "sha512-34+xZ2u4ys/aUzWCU9m6Eee4nVuN1ywdxbi8b3Z2WULU6qvnfeHvCWEdGzlVfRbbhimG2xxJX6R77GD2cuVO6w==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/blst": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/blst/-/blst-0.1.5.tgz",
"integrity": "sha512-TPl04Cx3CHdPFAJ2x9Xx1Z1FOfpAzmNPfHkfo+pGAaNH4uLhS58ExvamVkZh3jadF+B7V5sMtqvrqdf9mHINYA==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bmocha": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/bmocha/-/bmocha-2.1.5.tgz",
"integrity": "sha512-hEO+jQC+6CMxdxSqKPjqAdIDvRWHfdGgsMh4fUmatkMewbYr2O6qMIbW7Lhcmkcnz8bwRHZuEdDaBt/16NofoA==",
"dev": true,
"bin": {
"_bmocha": "bin/_bmocha",
"bmocha": "bin/bmocha"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bmutex": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/bmutex/-/bmutex-0.1.6.tgz",
"integrity": "sha512-nXWOXtQHbfPaMl6jyEF/rmRMrcemj2qn+OCAI/uZYurjfx7Dg3baoXdPzHOL0U8Cfvn8CWxKcnM/rgxL7DR4zw==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bns": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/bns/-/bns-0.15.0.tgz",
"integrity": "sha512-iJWQVE399vQzPfhalFMJGEQ7k5Ot2D6Mz8dkoPeLO8huWAMOiJNJ1tHzOu5j+ZyNNew6ITgG/LsSyaRPxvkXuw==",
"dependencies": {
"bcrypto": "~5.4.0",
"bfile": "~0.2.2",
"bheep": "~0.1.5",
"binet": "~0.3.6",
"bs32": "~0.1.6",
"bsert": "~0.0.10",
"btcp": "~0.1.5",
"budp": "~0.1.6",
"bufio": "~1.0.7"
},
"bin": {
"bns-keygen": "bin/bns-keygen",
"bns-prove": "bin/bns-prove",
"dig.js": "bin/dig.js",
"dig2json": "bin/dig2json",
"json2dig": "bin/json2dig",
"json2rr": "bin/json2rr",
"json2zone": "bin/json2zone",
"named.js": "bin/named.js",
"rr2json": "bin/rr2json",
"whois.js": "bin/whois.js",
"zone2json": "bin/zone2json"
},
"engines": {
"node": ">=8.0.0"
},
"optionalDependencies": {
"unbound": "~0.4.3"
}
},
"node_modules/brq": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/brq/-/brq-0.1.8.tgz",
"integrity": "sha512-6SDY1lJMKXgt5TZ6voJQMH2zV1XPWWtm203PSkx3DSg9AYNYuRfOPFSBDkNemabzgpzFW9/neR4YhTvyJml8rQ==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bs32": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/bs32/-/bs32-0.1.6.tgz",
"integrity": "sha512-usjDesQqZ8ihHXOnOEQuAdymBHnJEfSd+aELFSg1jN/V3iAf12HrylHlRJwIt6DTMmXpBDQ+YBg3Q3DIYdhRgQ==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bsert": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/bsert/-/bsert-0.0.10.tgz",
"integrity": "sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bsock": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/bsock/-/bsock-0.1.9.tgz",
"integrity": "sha512-/l9Kg/c5o+n/0AqreMxh2jpzDMl1ikl4gUxT7RFNe3A3YRIyZkiREhwcjmqxiymJSRI/Qhew357xGn1SLw/xEw==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bsocks": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bsocks/-/bsocks-0.2.6.tgz",
"integrity": "sha512-66UkjoB9f7lhT+WKgYq8MQa6nkr96mlX64JYMlIsXe/X4VeqNwvsx7UOE3ZqD6lkwg8GvBhapRTWj0qWO3Pw8w==",
"dependencies": {
"binet": "~0.3.5",
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/btcp": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/btcp/-/btcp-0.1.5.tgz",
"integrity": "sha512-tkrtMDxeJorn5p0KxaLXELneT8AbfZMpOFeoKYZ5qCCMMSluNuwut7pGccLC5YOJqmuk0DR774vNVQLC9sNq/A==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/budp": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/budp/-/budp-0.1.6.tgz",
"integrity": "sha512-o+a8NPq3DhV91j4nInjht2md6mbU1XL+7ciPltP66rw5uD3KP1m5r8lA94LZVaPKcFdJ0l2HVVzRNxnY26Pefg==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/buffer-map": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/buffer-map/-/buffer-map-0.0.7.tgz",
"integrity": "sha512-95try3p/vMRkIAAnJDaGkFhGpT/65NoeW6XelEPjAomWYR58RQtW4khn0SwKj34kZoE7uxL7w2koZSwbnszvQQ==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bufio": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz",
"integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bupnp": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bupnp/-/bupnp-0.2.6.tgz",
"integrity": "sha512-J6ykzJhZMxXKN78K+1NzFi3v/51X2Mvzp2hW42BWwmxIVfau6PaN99gyABZ8x05e8MObWbsAis23gShhj9qpbw==",
"dependencies": {
"binet": "~0.3.5",
"brq": "~0.1.7",
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bval": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/bval/-/bval-0.1.6.tgz",
"integrity": "sha512-jxNH9gSx7g749hQtS+nTxXYz/bLxwr4We1RHFkCYalNYcj12RfbW6qYWsKu0RYiKAdFcbNoZRHmWrIuXIyhiQQ==",
"dependencies": {
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bweb": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/bweb/-/bweb-0.1.10.tgz",
"integrity": "sha512-3Kkz/rfsyAWUS+8DV5XYhwcgVN4DfDewrP+iFTcpQfdZzcF6+OypAq7dHOtXV0sW7U/3msA/sEEqz0MHZ9ERWg==",
"dependencies": {
"bsert": "~0.0.10",
"bsock": "~0.1.8"
},
"bin": {
"bweb": "bin/bweb"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/goosig": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/goosig/-/goosig-0.10.0.tgz",
"integrity": "sha512-+BVVLfxmawAmGVjjJpXzu5LNcFIOfgXgP7kWEyc3qu/xn9RMqbPbNfYDdHBZKfZkDMIO7Q4vD790iNYQAXhoFA==",
"hasInstallScript": true,
"dependencies": {
"bcrypto": "~5.4.0",
"bsert": "~0.0.10",
"loady": "~0.0.5"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/hs-client": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/hs-client/-/hs-client-0.0.10.tgz",
"integrity": "sha512-15tfeQEMRS1FZA0q9gFbQ1jYs8v4z9oKw9xFwVEyRuckn72hoVAglN4IrFxkOCDMYV7TWCY/nO/yNZp5njYFBw==",
"dependencies": {
"bcfg": "~0.1.7",
"bcurl": "~0.1.9",
"bsert": "~0.0.10"
},
"bin": {
"hsd-cli": "bin/hsd-cli",
"hsd-rpc": "bin/hsd-rpc",
"hsw-cli": "bin/hsw-cli",
"hsw-rpc": "bin/hsw-rpc"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/loady": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/loady/-/loady-0.0.5.tgz",
"integrity": "sha512-uxKD2HIj042/HBx77NBcmEPsD+hxCgAtjEWlYNScuUjIsh/62Uyu39GOR68TBR68v+jqDL9zfftCWoUo4y03sQ==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/mrmr": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/mrmr/-/mrmr-0.1.10.tgz",
"integrity": "sha512-NJRJs+yJyRWwcTqLRf7O32n56UP1+UQoTrGVEoB3LMj0h2jlon790drDbxKvi5mK5k4HfC0cpNkxqHcrJK/evg==",
"hasInstallScript": true,
"dependencies": {
"bsert": "~0.0.10",
"loady": "~0.0.5"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/n64": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/n64/-/n64-0.2.10.tgz",
"integrity": "sha512-uH9geV4+roR1tohsrrqSOLCJ9Mh1iFcDI+9vUuydDlDxUS1UCAWUfuGb06p3dj3flzywquJNrGsQ7lHP8+4RVQ==",
"engines": {
"node": ">=2.0.0"
}
},
"node_modules/unbound": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/unbound/-/unbound-0.4.3.tgz",
"integrity": "sha512-2ISqZLXtzp1l9f1V8Yr6S+zuhXxEwE1CjKHjXULFDHJcfhc9Gm3mn19hdPp4rlNGEdCivKYGKjYe3WRGnafYdA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"loady": "~0.0.5"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/urkel": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/urkel/-/urkel-0.7.0.tgz",
"integrity": "sha512-7Z3Gor4DkKKi0Ehp6H9xehWXqyL12+PA4JM41dcVc1LWks4zI4PGWv6DWgxaLCC+otpEuGdq3Vh5ayD/Mvzfbg==",
"dependencies": {
"bfile": "~0.2.1",
"bmutex": "~0.1.6",
"bsert": "~0.0.10"
},
"engines": {
"node": ">=8.0.0"
}
}
},
"dependencies": {
"bcfg": {
"version": "0.1.7",
@ -80,9 +551,9 @@
}
},
"binet": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/binet/-/binet-0.3.6.tgz",
"integrity": "sha512-6pm+Gc3uNiiJZEv0k8JDWqQlo9ki/o9UNAkLmr0EGm7hI5MboOJVIOlO1nw3YuDkLHWN78OPsaC4JhRkn2jMLw==",
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/binet/-/binet-0.3.7.tgz",
"integrity": "sha512-GF+QD4ajs3GWabaVzso7Kn9aZEbwI0e54FKU2ID8bM/7rIk7BpSJytB1KS7SMpix+fWAi9MAGkOgSFljl0aaKg==",
"requires": {
"bs32": "~0.1.5",
"bsert": "~0.0.10"

View file

@ -28,7 +28,7 @@
"bfile": "~0.2.2",
"bfilter": "~1.0.5",
"bheep": "~0.1.5",
"binet": "~0.3.6",
"binet": "~0.3.7",
"blgr": "~0.2.0",
"blru": "~0.1.6",
"blst": "~0.1.5",

View file

@ -69,6 +69,17 @@ describe('Chain Timelocks', function() {
});
describe('Relative (CSV)', function() {
let timeOffset;
// make sure we recover proper regtest Network.
before(() => {
timeOffset = network.time.offset;
});
after(() => {
network.time.offset = timeOffset;
});
// Relative timelock by height
const csvHeightScript = new Script([
Opcode.fromInt(2),

View file

@ -0,0 +1,335 @@
'use strict';
const Network = require('../../lib/protocol/network');
const netaddressVectors = exports;
const main = Network.get('main');
const regtest = Network.get('regtest');
// [passedOptions, expectedValues]
netaddressVectors.options = [
[null, {
host: '0.0.0.0',
port: 0,
hostname: '0.0.0.0:0',
isNull: true,
isIPv6: false,
isLocal: true,
isValid: false,
isRoutable: false
}],
[{
host: '0.0.0.0',
port: 0
}, {
host: '0.0.0.0',
port: 0,
hostname: '0.0.0.0:0',
isNull: true,
isIPv6: false,
isLocal: true,
isValid: false,
isRoutable: false
}],
[{
host: '2345:0425:2CA1:0000:0000:0567:5673:23b5',
port: 1000
}, {
host: '2345:425:2ca1::567:5673:23b5',
port: 1000,
hostname: '[2345:425:2ca1::567:5673:23b5]:1000',
isIPv6: true,
isLocal: false,
isValid: true,
isRoutable: true
}],
[{
host: '1.1.1.1',
port: 1,
services: 1,
key: Buffer.alloc(33, 1)
}, {
hostname:
'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc@1.1.1.1:1',
key: Buffer.alloc(33, 1),
services: 1,
isIPv6: false,
isLocal: false,
isValid: true,
isRoutable: true
}],
[{
host: '2.2.2.2',
port: 2,
key: Buffer.alloc(33, 2),
services: 2
}, {
hostname:
'aibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibae@2.2.2.2:2',
key: Buffer.alloc(33, 2),
services: 2,
isIPv6: false,
isLocal: false,
isValid: true,
isRoutable: true
}], [{
host: '127.0.0.1',
port: 1000,
services: 3
}, {
hostname: '127.0.0.1:1000',
services: 3,
isIPv6: false,
isLocal: true,
isValid: true,
isRoutable: false
}], [{
host: '127.1.1.1',
port: 1000
}, {
hostname: '127.1.1.1:1000',
isIPv6: false,
isLocal: true,
isValid: true,
isRoutable: false
}], [{
host: '::1',
port: 1000
}, {
hostname: '[::1]:1000',
isIPv6: true,
isLocal: true,
isValid: true,
isRoutable: false
}], [{
host: 'fd87:d87e:eb43::1',
port: 1000
}, {
host: 'aaaaaaaaaaaaaaab.onion',
hostname: 'aaaaaaaaaaaaaaab.onion:1000',
isIPv6: false,
isIPV4: false,
isLocal: false,
isValid: true,
isRoutable: true,
isOnion: true
}]
];
const goodOptions = {
host: '0.0.0.0',
port: 12038
};
// [passedOptions, message]
netaddressVectors.failOptions = [
[{ port: goodOptions.port }, 'NetAddress requires host string.'],
[{ host: 1234 }, 'NetAddress requires host string.'],
[{ host: goodOptions.host }, 'NetAddress requires port number.'],
[{ host: goodOptions.host, port: '32' },
'NetAddress requires port number.'],
[{ host: goodOptions.host, port: -1 }, 'port number is incorrect.'],
[{ host: goodOptions.host, port: 0xffff + 1 },
'port number is incorrect.'],
[{ ...goodOptions, services: '12' }, 'services must be a number.'],
[{ ...goodOptions, services: {} }, 'services must be a number.'],
[{ ...goodOptions, key: '12' }, 'key must be a buffer.'],
[{ ...goodOptions, key: 11 }, 'key must be a buffer.']
];
// [options, expected]
netaddressVectors.fromHost = [
[
['172.104.214.189', 1000, Buffer.alloc(33, 1)],
{
host: '172.104.214.189',
port: 1000,
key: Buffer.alloc(33, 1),
hostname: 'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc'
+ '@172.104.214.189:1000'
}
],
[
['15.152.162.66', 1001, Buffer.alloc(33, 2)],
{
host: '15.152.162.66',
port: 1001,
key: Buffer.alloc(33, 2),
hostname: 'aibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibae'
+ '@15.152.162.66:1001'
}
],
[
['2345:0425:2CA1:0000:0000:0567:5673:23b5', 0xffff],
{
host: '2345:425:2ca1::567:5673:23b5',
port: 0xffff,
key: Buffer.alloc(33, 0),
hostname: '[2345:425:2ca1::567:5673:23b5]:65535'
}
]
];
// [options, expected]
netaddressVectors.fromHostname = [
[['127.0.0.1:100', 'main'], {
hostname: '127.0.0.1:100',
host: '127.0.0.1',
port: 100,
key: Buffer.alloc(33, 0)
}],
[
[
'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc@127.0.0.1:100',
'main'
], {
hostname: 'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc'
+ '@127.0.0.1:100',
host: '127.0.0.1',
port: 100,
key: Buffer.alloc(33, 1)
}
],
[['127.0.0.1', 'main'], {
hostname: `127.0.0.1:${main.port}`,
host: '127.0.0.1',
port: main.port,
key: Buffer.alloc(33, 0)
}],
[
[
'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc@127.0.0.1',
'main'
], {
hostname: 'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc'
+ `@127.0.0.1:${main.brontidePort}`,
host: '127.0.0.1',
port: main.brontidePort,
key: Buffer.alloc(33, 1)
}
],
[['127.0.0.1', 'regtest'], {
hostname: `127.0.0.1:${regtest.port}`,
host: '127.0.0.1',
port: regtest.port,
key: Buffer.alloc(33, 0)
}],
[
[
'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc@127.0.0.1',
'regtest'
], {
hostname: 'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc'
+ `@127.0.0.1:${regtest.brontidePort}`,
host: '127.0.0.1',
port: regtest.brontidePort,
key: Buffer.alloc(33, 1)
}
]
];
// [args, expected]
netaddressVectors.fromSocket = [
[[{
remoteAddress: '2001:4860:a005::68',
remotePort: 1000
}, 'main'], {
hostname: '[2001:4860:a005::68]:1000',
host: '2001:4860:a005::68',
port: 1000
}],
[[{
remoteAddress: '74.125.127.100',
remotePort: 2000
}, 'main'], {
hostname: '74.125.127.100:2000',
host: '74.125.127.100',
port: 2000
}]
];
// [addrA, addrB, expectedCompareResults]
netaddressVectors.compare = [
[['127.0.0.1', 10], ['127.1.1.1', 9], -1],
[['0.0.0.0', 10], ['1.1.1.1', 9], -1],
[['0.0.0.1', 10], ['0.0.0.1', 9], 1],
// IPV4 has two 0xff in the buffer before last 4 bytes.
// So any IPV6 from ::1 to :ffff:0:0 will be lower than IPV4.
// And any IPV6 from :ffff:0:0 to :ffff:ffff:ffff will be IPV4.
[['::1', 1], ['0.0.0.1', 1], -1],
[['::ffff:ffff', 1], ['0.0.0.1', 1], -1],
[['::ffff:ffff:ffff', 1], ['0.0.0.1', 1], 1],
[['::ffff:0:1', 1], ['0.0.0.1', 1], 0],
[['::ffff:ffff:ffff', 1], ['255.255.255.255', 1], 0],
// If IPs are same, then we compare ports.
[['::1', 102], ['::1', 101], 1],
[['::1', 100], ['::1', 101], -1],
[['::1', 100], ['::1', 100], 0]
];
// Reachability scores
const rscores = {
UNREACHABLE: 0,
DEFAULT: 1,
TEREDO: 2,
IPV6_WEAK: 3,
IPV4: 4,
IPV6_STRONG: 5,
PRIVATE: 6
};
// reachability IPs
const rips = {
ipv4: {
src: '74.125.127.100',
dest: '45.79.134.225'
},
ipv6: {
src: 'ffff::1',
dest: 'ffff::ffff'
},
onion: {
src: 'aaaaaaaaaaaaaaaa.onion',
dest: 'bbbbbbbbbbbbbbbb.onion'
},
teredo: {
src: '2001::1',
dest: '2001:ffff::1'
}
};
netaddressVectors.getReachability = [
// unroutable, destination does not matter
['127.0.0.1', rips.ipv4.dest, rscores.UNREACHABLE],
// IPv4 dest - DEFAULT
[rips.ipv4.src, rips.ipv4.dest, rscores.IPV4],
[rips.ipv6.src, rips.ipv4.dest, rscores.DEFAULT],
[rips.onion.src, rips.ipv4.dest, rscores.DEFAULT],
[rips.teredo.src, rips.ipv4.dest, rscores.DEFAULT],
// IPv6 dest
[rips.ipv4.src, rips.ipv6.dest, rscores.IPV4],
['2002::1', rips.ipv6.dest, rscores.IPV6_WEAK],
[rips.ipv6.src, rips.ipv6.dest, rscores.IPV6_STRONG],
[rips.onion.src, rips.ipv6.dest, rscores.DEFAULT],
[rips.teredo.src, rips.ipv6.dest, rscores.TEREDO],
// ONION Dest
[rips.ipv4.src, rips.onion.src, rscores.IPV4],
[rips.ipv6.src, rips.onion.src, rscores.DEFAULT],
[rips.onion.src, rips.onion.src, rscores.PRIVATE],
[rips.teredo.src, rips.onion.src, rscores.DEFAULT],
// TEREDO Dest
[rips.ipv4.src, rips.teredo.src, rscores.IPV4],
[rips.ipv6.src, rips.teredo.src, rscores.IPV6_WEAK],
[rips.onion.src, rips.teredo.src, rscores.DEFAULT],
[rips.teredo.src, rips.teredo.src, rscores.TEREDO],
// UNREACHABLE Dest
[rips.ipv4.src, '127.0.0.1', rscores.IPV4],
[rips.ipv6.src, '127.0.0.1', rscores.IPV6_WEAK],
[rips.onion.src, '127.0.0.1', rscores.PRIVATE],
[rips.teredo.src, '127.0.0.1', rscores.TEREDO]
];

1782
test/net-hostlist-test.js Normal file

File diff suppressed because it is too large Load diff

55
test/net-lookup-test.js Normal file
View file

@ -0,0 +1,55 @@
'use strict';
const assert = require('bsert');
const Network = require('../lib/protocol/network');
const {lookup, resolve} = require('../lib/net/lookup');
const main = Network.get('main');
const notAHost = 'not-a-domain.not-a-domain';
describe('Lookup', function() {
this.timeout(10000);
it('should lookup seed', async () => {
for (const host of main.seeds) {
const addresses = await lookup(host);
assert(addresses.length > 0, 'addresses not found.');
}
});
it('should fail lookup', async () => {
let err;
try {
await lookup(notAHost);
} catch (e) {
err = e;
}
assert(err);
assert.strictEqual(err.message, 'No DNS results.');
});
it('should lookup seed', async () => {
for (const host of main.seeds) {
const addresses = await resolve(host);
assert(addresses.length > 0, 'addresses not found.');
}
});
it('should fail resolve', async () => {
let err;
try {
await resolve(notAHost);
} catch (e) {
err = e;
}
assert(err);
assert.strictEqual(err.message, `Query error: NXDOMAIN (${notAHost} A).`);
// TODO: Host that does not have A/AAAA records?
});
});

515
test/net-netaddress-test.js Normal file
View file

@ -0,0 +1,515 @@
'use strict';
/* Parts of this software are based on bitcoin/bitcoin:
* Copyright (c) 2009-2019, The Bitcoin Core Developers (MIT License).
* Copyright (c) 2009-2019, The Bitcoin Developers (MIT License).
* https://github.com/bitcoin/bitcoin
*
* Resources:
* https://github.com/bitcoin/bitcoin/blob/46fc4d1a24c88e797d6080336e3828e45e39c3fd/src/test/netbase_tests.cpp
*/
const assert = require('bsert');
const NetAddress = require('../lib/net/netaddress');
const Network = require('../lib/protocol/network');
const util = require('../lib/utils/util');
const netaddressVectors = require('./data/netaddress-data');
// 16 bytes (ipv6) - 4 (ipv4) byte - 2 ff = 10
const IPV4_PREFIX = Buffer.from(`${'00'.repeat(10)}ffff`, 'hex');
const main = Network.get('main');
describe('NetAddress', function() {
it('should parse options', () => {
const {options} = netaddressVectors;
for (const [i, [opts, expected]] of options.entries()) {
const naddr = new NetAddress(opts);
if (expected.host == null)
expected.host = opts.host;
if (expected.port == null)
expected.port = opts.port;
assert.strictEqual(naddr.host, expected.host, `Failed #${i}`);
assert.strictEqual(naddr.port, expected.port, `Failed #${i}`);
assert.strictEqual(naddr.hostname, expected.hostname, `Failed #${i}`);
const expectedKey = opts && opts.key;
assert.strictEqual(naddr.hasKey(), Boolean(expectedKey), `Failed #${i}`);
if (expectedKey)
assert.bufferEqual(naddr.key, expectedKey, `Failed #${i}`);
if (expected.isIPv6 != null) {
const isIPV4 = expected.isIPV4 != null
? expected.isIPV4
: !expected.isIPv6;
assert.strictEqual(naddr.isIPv4(), isIPV4, `Failed #${i}`);
assert.strictEqual(naddr.isIPv6(), expected.isIPv6, `Failed #${i}`);
}
if (opts && opts.services != null) {
assert.strictEqual(true, naddr.hasServices(expected.services),
`Failed #${i}`);
}
assert.strictEqual(naddr.isRoutable(), expected.isRoutable, `Failed #${i}`);
assert.strictEqual(naddr.isValid(), expected.isValid, `Failed #${i}`);
assert.strictEqual(naddr.isNull(), Boolean(expected.isNull),
`Failed #${i}`);
assert.strictEqual(naddr.isOnion(), Boolean(expected.isOnion),
`Failed #${i}`);
assert.strictEqual(true, naddr.equal(naddr), `Failed #${i}`);
assert.strictEqual(naddr.isLocal(), expected.isLocal, `Failed #${i}`);
}
});
it('should fail parsing options', () => {
const {failOptions} = netaddressVectors;
for (const [opts, msg] of failOptions) {
let err;
try {
new NetAddress(opts);
} catch (e) {
err = e;
}
assert(err, 'Expected err');
assert.strictEqual(err.message, msg);
}
});
it('should check services', async () => {
const naddr = new NetAddress();
const serviceList = [];
for (let i = 0; i < 10; i++)
serviceList.push(1 << i);
naddr.services = serviceList[7] | serviceList[8] | serviceList[9];
for (let i = 7; i < 10; i++)
assert.strictEqual(true, naddr.hasServices(serviceList[i]));
for (let i = 0; i < 7; i++)
assert.strictEqual(false, naddr.hasServices(serviceList[i]));
assert.strictEqual(true,
naddr.hasServices(serviceList[7] | serviceList[8]));
assert.strictEqual(false,
naddr.hasServices(serviceList[1] | serviceList[8]));
});
it('should set null', async () => {
const oldRaw = Buffer.from('2d4f86e1', 'hex');
const nullRaw = Buffer.alloc(4, 0);
const naddr = new NetAddress({
host: '45.79.134.225',
port: 1
});
assert.strictEqual(false, naddr.isNull());
assert.bufferEqual(naddr.raw, Buffer.concat([IPV4_PREFIX, oldRaw]));
assert.strictEqual(naddr.hostname, '45.79.134.225:1');
naddr.setNull();
assert.strictEqual(true, naddr.isNull());
assert.bufferEqual(naddr.raw, Buffer.concat([IPV4_PREFIX, nullRaw]));
assert.strictEqual(naddr.hostname, '0.0.0.0:1');
});
it('should set host', async () => {
const oldHost = '45.79.134.225';
const oldRaw = Buffer.from('2d4f86e1', 'hex');
const newHost = '15.152.112.161';
const newRaw = Buffer.from('0f9870a1', 'hex');
const naddr = new NetAddress({
host: oldHost,
port: 12038
});
assert.strictEqual(naddr.host, oldHost);
assert.bufferEqual(naddr.raw, Buffer.concat([IPV4_PREFIX, oldRaw]));
naddr.setHost(newHost);
assert.strictEqual(naddr.host, newHost);
assert.bufferEqual(naddr.raw, Buffer.concat([IPV4_PREFIX, newRaw]));
});
it('should set port', async () => {
const naddr = new NetAddress({
host: '45.79.134.225',
port: 1000
});
const badPorts = [
-1,
-0xffff,
0xffff + 1,
0xffffff
];
for (const port of badPorts) {
let err;
try {
naddr.setPort(port);
} catch (e) {
err = e;
}
assert(err, `Error not found for ${port}.`);
}
const goodPorts = [
0,
0xffff,
12038,
44806
];
for (const port of goodPorts) {
naddr.setPort(port);
assert.strictEqual(naddr.port, port);
assert.strictEqual(naddr.hostname, `${naddr.host}:${port}`);
}
});
it('should set/get key', async () => {
const testKey = Buffer.alloc(33, 1);
const naddr = new NetAddress({
host: '0.0.0.0',
port: 1000
});
assert.strictEqual(naddr.getKey(), null);
naddr.setKey(testKey);
assert.bufferEqual(naddr.getKey(), testKey);
assert.strictEqual(naddr.getKey('base32'),
'aeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqcaibaeaqc');
assert.strictEqual(naddr.getKey('hex'),
'01'.repeat(33));
assert.strictEqual(naddr.hostname,
`${naddr.getKey('base32')}@${naddr.host}:${naddr.port}`);
naddr.setKey();
assert.strictEqual(naddr.getKey(), null);
assert.strictEqual(naddr.getKey('base32'), null);
assert.strictEqual(naddr.getKey('hex'), null);
assert.strictEqual(naddr.hostname, `${naddr.host}:${naddr.port}`);
const badKeys = [
'badkey',
Buffer.alloc(32, 0),
Buffer.alloc(34, 11)
];
for (const key of badKeys) {
let err;
try {
naddr.setKey(key);
} catch (e) {
err = e;
}
assert(err);
}
});
it('should create from host', () => {
const {fromHost} = netaddressVectors;
const naddr = new NetAddress();
for (const [params, expected] of fromHost) {
naddr.fromHost(...params);
const naddr2 = NetAddress.fromHost(...params);
for (const addr of [naddr, naddr2]) {
assert.strictEqual(addr.host, expected.host);
assert.strictEqual(addr.port, expected.port);
assert.bufferEqual(addr.key, expected.key);
assert.strictEqual(addr.hostname, expected.hostname);
}
}
});
it('should create from hostname', () => {
const {fromHostname} = netaddressVectors;
for (const [args, expected] of fromHostname) {
const addr1 = new NetAddress();
addr1.fromHostname(...args);
const addr2 = NetAddress.fromHostname(...args);
for (const addr of [addr1, addr2]) {
assert.strictEqual(addr.hostname, expected.hostname);
assert.strictEqual(addr.host, expected.host);
assert.strictEqual(addr.port, expected.port);
assert.bufferEqual(addr.key, expected.key);
}
}
});
it('should create from socket', () => {
const {fromSocket} = netaddressVectors;
for (const [args, expected] of fromSocket) {
const addr = NetAddress.fromSocket(...args);
assert.strictEqual(addr.hostname, expected.hostname);
assert.strictEqual(addr.host, expected.host);
assert.strictEqual(addr.port, expected.port);
}
});
it('should compare addresses', () => {
const {compare} = netaddressVectors;
for (const [[hosta, porta], [hostb, portb], expected] of compare) {
const addrA = new NetAddress({
host: hosta,
port: porta
});
const addrB = new NetAddress({
host: hostb,
port: portb
});
assert.strictEqual(addrA.compare(addrB), expected,
`Failed for ${hosta}:${portb} compare to ${hostb}:${portb}.`);
}
});
it('should serialize/deserialize raw', () => {
const options = {
host: '::1',
port: 1000,
services: 0xff,
time: main.now(),
key: Buffer.alloc(33, 1)
};
const check = (addr, incorrectHost) => {
if (incorrectHost)
assert.notStrictEqual(addr.host, options.host);
else
assert.strictEqual(addr.host, options.host);
assert.strictEqual(addr.port, options.port);
assert.strictEqual(addr.time, options.time);
assert.strictEqual(addr.services, options.services);
assert.bufferEqual(addr.key, options.key);
};
{
const addr = new NetAddress(options);
const encoded = addr.encode();
const decoded = NetAddress.decode(encoded);
assert.strictEqual(decoded.equal(addr), true);
assert.strictEqual(decoded.compare(addr), 0);
assert.bufferEqual(decoded.encode(), encoded);
check(decoded);
}
{
// Do not decode IP.
const addr = new NetAddress(options);
const encoded = addr.encode();
// time(8) + services(4) + service bits(4)
encoded[8 + 8] = 1;
const decoded = NetAddress.decode(encoded);
assert.strictEqual(decoded.equal(addr), false);
assert.strictEqual(decoded.compare(addr), -1);
assert.notBufferEqual(decoded.encode(), encoded);
check(decoded, true);
}
});
it('should serialize/deserialize JSON', () => {
const options = {
host: '::1',
port: 1000,
services: 0xff,
time: main.now(),
key: Buffer.alloc(33, 1)
};
const check = (addr, hex) => {
assert.strictEqual(addr.host, options.host);
assert.strictEqual(addr.port, options.port);
assert.strictEqual(addr.time, options.time);
assert.strictEqual(addr.services, options.services);
if (hex)
assert.strictEqual(addr.key, options.key.toString('hex'));
else
assert.bufferEqual(addr.key, options.key);
};
const addr = new NetAddress(options);
const json = addr.getJSON();
const decoded = NetAddress.fromJSON(json);
assert.strictEqual(decoded.equal(addr), true);
assert.strictEqual(decoded.compare(addr), 0);
assert.bufferEqual(decoded.encode(), addr.encode());
check(decoded);
check(json, true);
});
it('should inspect/format', () => {
const options = {
host: '::1',
port: 1000,
services: 0xff,
time: main.now(),
key: Buffer.alloc(33, 1)
};
const addr = new NetAddress(options);
const formatted = addr.format();
assert.strictEqual(formatted.startsWith('<NetAddress'), true);
assert.strictEqual(formatted.endsWith('>'), true);
assert.strictEqual(formatted.indexOf(`hostname=${addr.hostname}`) > 0,
true);
assert.strictEqual(
formatted.indexOf(`services=${addr.services.toString(2)}`) > 0,
true
);
assert.strictEqual(formatted.indexOf(`date=${util.date(addr.time)}`) > 0,
true);
});
it('should get reachability score', () => {
// see: binet.getReachability for details.
// tests for the getReachability are covered in binet.
//
// Here we only test single case for all.
const {getReachability} = netaddressVectors;
for (const [source, destination, reachability] of getReachability) {
const src = new NetAddress({
host: source,
port: 1000
});
const dest = new NetAddress({
host: destination,
port: 1000
});
assert.strictEqual(src.getReachability(dest), reachability,
`${source}->${destination} - ${reachability}`);
}
});
it('should return the correct group', () => {
// Local -> !Routable()
assert.bufferEqual(
NetAddress.fromHost('127.0.0.1', 13038, null, 'testnet').getGroup(),
Buffer.from([0xff])
);
// RFC1918 -> !Routable()
assert.bufferEqual(
NetAddress.fromHost('169.254.1.1', 13038, null, 'testnet').getGroup(),
Buffer.from([0])
);
// IPv4
assert.bufferEqual(
NetAddress.fromHost('1.2.3.4', 13038, null, 'testnet').getGroup(),
Buffer.from([1, 1, 2])
);
// RFC6145
assert.bufferEqual(
NetAddress.fromHost(
'::FFFF:0:102:304',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([1, 1, 2])
);
// RFC6052
assert.bufferEqual(
NetAddress.fromHost(
'64:FF9B::102:304',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([1, 1, 2])
);
// RFC3964
assert.bufferEqual(
NetAddress.fromHost(
'2002:102:304:9999:9999:9999:9999:9999',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([1, 1, 2])
);
// RFC4380
assert.bufferEqual(
NetAddress.fromHost(
'2001:0:9999:9999:9999:9999:FEFD:FCFB',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([1, 1, 2])
);
// Tor
assert.bufferEqual(
NetAddress.fromHost(
'FD87:D87E:EB43:edb1:8e4:3588:e546:35ca',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([3, 239])
);
// he.net
assert.bufferEqual(
NetAddress.fromHost(
'2001:470:abcd:9999:9999:9999:9999:9999',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([2, 32, 1, 4, 112, 175])
);
// IPv6
assert.bufferEqual(
NetAddress.fromHost(
'2001:2001:9999:9999:9999:9999:9999:9999',
13038,
null,
'testnet'
).getGroup(),
Buffer.from([2, 32, 1, 32, 1])
);
});
});