net: improvements. see comments.
- Improved local address handling. - Enabled upnp discovery. - Added retry for dns seeds. - Improved hostlist in general.
This commit is contained in:
parent
614a952015
commit
cdbad54a8c
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var net = require('net');
|
||||
var dns = require('dns');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var IOServer = require('socket.io');
|
||||
var util = require('../lib/utils/util');
|
||||
@ -65,47 +64,6 @@ WSProxy.prototype._handleSocket = function _handleSocket(ws) {
|
||||
ws.on('tcp connect', function(port, host, nonce) {
|
||||
self._handleConnect(ws, port, host, nonce);
|
||||
});
|
||||
|
||||
ws.on('dns resolve', function(name, record, callback) {
|
||||
self._handleResolve(ws, name, record, callback);
|
||||
});
|
||||
};
|
||||
|
||||
WSProxy.prototype._handleResolve = function _handleResolve(ws, name, record, callback) {
|
||||
if (typeof name !== 'string') {
|
||||
ws.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof record !== 'string') {
|
||||
ws.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
ws.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (record !== 'A' && record !== 'AAAA') {
|
||||
this.log('Client sent a bad record type: %s.', record);
|
||||
ws.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NAME_REGEX.test(name) || name.length > 200) {
|
||||
this.log('Client sent a bad domain: %s.', name);
|
||||
ws.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
dns.resolve(name, record, function(err, result) {
|
||||
if (err) {
|
||||
callback({ message: err.message, code: err.code });
|
||||
return;
|
||||
}
|
||||
callback(null, result);
|
||||
});
|
||||
};
|
||||
|
||||
WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce) {
|
||||
|
||||
@ -63,6 +63,7 @@ max-inbound: 30
|
||||
# Proxy Server (browser=websockets, node=socks)
|
||||
# proxy: foo:bar@127.0.0.1:9050
|
||||
# onion: true
|
||||
# upnp: true
|
||||
|
||||
# Custom list of DNS seeds
|
||||
# seeds: seed.bitcoin.sipa.be
|
||||
|
||||
@ -386,7 +386,7 @@ RPC.prototype.getnetworkinfo = co(function* getnetworkinfo(args, help) {
|
||||
version: pkg.version,
|
||||
subversion: this.pool.options.agent,
|
||||
protocolversion: this.pool.options.version,
|
||||
localservices: this.pool.address.services,
|
||||
localservices: this.pool.options.services,
|
||||
timeoffset: this.network.time.offset,
|
||||
connections: this.pool.peers.size(),
|
||||
networks: [],
|
||||
@ -522,7 +522,9 @@ RPC.prototype.getpeerinfo = co(function* getpeerinfo(args, help) {
|
||||
peers.push({
|
||||
id: id++,
|
||||
addr: peer.hostname(),
|
||||
addrlocal: peer.hostname(),
|
||||
addrlocal: !peer.local.isNull()
|
||||
? peer.local.hostname
|
||||
: undefined,
|
||||
relaytxes: !peer.noRelay,
|
||||
lastsend: peer.lastSend / 1000 | 0,
|
||||
lastrecv: peer.lastRecv / 1000 | 0,
|
||||
|
||||
@ -623,6 +623,10 @@ HTTPServer.prototype._init = function _init() {
|
||||
this.get('/', co(function* (req, res) {
|
||||
var totalTX = this.mempool ? this.mempool.totalTX : 0;
|
||||
var size = this.mempool ? this.mempool.getSize() : 0;
|
||||
var addr = this.pool.hosts.getLocal();
|
||||
|
||||
if (!addr)
|
||||
addr = this.pool.hosts.address;
|
||||
|
||||
res.send(200, {
|
||||
version: pkg.version,
|
||||
@ -633,10 +637,10 @@ HTTPServer.prototype._init = function _init() {
|
||||
progress: this.chain.getProgress()
|
||||
},
|
||||
pool: {
|
||||
host: this.pool.address.host,
|
||||
port: this.pool.address.port,
|
||||
host: addr.host,
|
||||
port: addr.port,
|
||||
agent: this.pool.options.agent,
|
||||
services: this.pool.address.services.toString(2),
|
||||
services: this.pool.options.services.toString(2),
|
||||
outbound: this.pool.peers.outbound,
|
||||
inbound: this.pool.peers.inbound
|
||||
},
|
||||
|
||||
@ -467,7 +467,7 @@ function AuthDB(options) {
|
||||
return new AuthDB(options);
|
||||
|
||||
this.logger = null;
|
||||
this.resolve = dns.resolve;
|
||||
this.resolve = dns.lookup;
|
||||
this.dnsKnown = [];
|
||||
|
||||
this.known = {};
|
||||
@ -476,6 +476,26 @@ function AuthDB(options) {
|
||||
this._init(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open auth database (lookup known peers).
|
||||
* @method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AuthDB.prototype.open = co(function* open() {
|
||||
yield this.lookup();
|
||||
});
|
||||
|
||||
/**
|
||||
* Close auth database.
|
||||
* @method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AuthDB.prototype.close = co(function* close() {
|
||||
;
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize authdb with options.
|
||||
* @param {Object} options
|
||||
@ -601,7 +621,7 @@ AuthDB.prototype.getKnown = function getKnown(hostname) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AuthDB.prototype.discover = co(function* discover() {
|
||||
AuthDB.prototype.lookup = co(function* lookup() {
|
||||
var jobs = [];
|
||||
var i, addr;
|
||||
|
||||
@ -610,8 +630,6 @@ AuthDB.prototype.discover = co(function* discover() {
|
||||
jobs.push(this.populate(addr[0], addr[1]));
|
||||
}
|
||||
|
||||
this.dnsKnown.length = 0;
|
||||
|
||||
yield Promise.all(jobs);
|
||||
});
|
||||
|
||||
|
||||
@ -6,36 +6,17 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ProxySocket = require('./proxysocket');
|
||||
var socket;
|
||||
|
||||
/**
|
||||
* Resolve host (no getaddrinfo).
|
||||
* @ignore
|
||||
* @param {String} host
|
||||
* @param {String?} proxy
|
||||
* @param {Boolean?} onion
|
||||
* @param {String?} proxy - Tor socks proxy.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
exports.resolve = function resolve(host, proxy, onion) {
|
||||
exports.resolve = function resolve(host, proxy) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (!socket)
|
||||
socket = new ProxySocket(proxy);
|
||||
|
||||
socket.resolve(host, 'A', function(err, result) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.length === 0) {
|
||||
reject(new Error('No DNS results.'));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
});
|
||||
reject(new Error('DNS not supported.'));
|
||||
});
|
||||
};
|
||||
|
||||
@ -43,11 +24,12 @@ exports.resolve = function resolve(host, proxy, onion) {
|
||||
* Resolve host (getaddrinfo).
|
||||
* @ignore
|
||||
* @param {String} host
|
||||
* @param {String?} proxy
|
||||
* @param {Boolean?} onion
|
||||
* @param {String?} proxy - Tor socks proxy.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
exports.lookup = function lookup(host, proxy, onion) {
|
||||
return exports.resolve(host, proxy, onion);
|
||||
exports.lookup = function lookup(host, proxy) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
reject(new Error('DNS not supported.'));
|
||||
});
|
||||
};
|
||||
|
||||
@ -22,13 +22,12 @@ var options = {
|
||||
/**
|
||||
* Resolve host (no getaddrinfo).
|
||||
* @param {String} host
|
||||
* @param {String?} proxy
|
||||
* @param {Boolean?} onion
|
||||
* @param {String?} proxy - Tor socks proxy.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
exports.resolve = function resolve(host, proxy, onion) {
|
||||
if (proxy && onion)
|
||||
exports.resolve = function resolve(host, proxy) {
|
||||
if (proxy)
|
||||
return socks.resolve(proxy, host);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
@ -51,13 +50,12 @@ exports.resolve = function resolve(host, proxy, onion) {
|
||||
/**
|
||||
* Resolve host (getaddrinfo).
|
||||
* @param {String} host
|
||||
* @param {String?} proxy
|
||||
* @param {Boolean?} onion
|
||||
* @param {String?} proxy - Tor socks proxy.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
exports.lookup = function lookup(host, proxy, onion) {
|
||||
if (proxy && onion)
|
||||
exports.lookup = function lookup(host, proxy) {
|
||||
if (proxy)
|
||||
return socks.resolve(proxy, host);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var util = require('../utils/util');
|
||||
var IP = require('../utils/ip');
|
||||
var co = require('../utils/co');
|
||||
@ -16,8 +17,9 @@ var List = require('../utils/list');
|
||||
var murmur3 = require('../utils/murmur3');
|
||||
var Map = require('../utils/map');
|
||||
var common = require('./common');
|
||||
var seeds = require('./seeds');
|
||||
var dns = require('./dns');
|
||||
var fs = require('fs');
|
||||
var Logger = require('../node/logger');
|
||||
|
||||
/**
|
||||
* Host List
|
||||
@ -30,45 +32,76 @@ function HostList(options) {
|
||||
if (!(this instanceof HostList))
|
||||
return new HostList(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = null;
|
||||
this.address = new NetAddress();
|
||||
this.resolve = dns.resolve;
|
||||
this.banTime = common.BAN_TIME;
|
||||
this.options = new HostListOptions(options);
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.address = this.options.address;
|
||||
this.resolve = this.options.resolve;
|
||||
|
||||
this.rawSeeds = this.network.seeds;
|
||||
this.dnsSeeds = [];
|
||||
this.rawNodes = [];
|
||||
this.dnsNodes = [];
|
||||
this.nodes = [];
|
||||
|
||||
this.banned = {};
|
||||
|
||||
this.map = {};
|
||||
this.fresh = [];
|
||||
this.used = [];
|
||||
|
||||
this.totalFresh = 0;
|
||||
this.used = [];
|
||||
this.totalUsed = 0;
|
||||
this.nodes = [];
|
||||
this.local = new Map();
|
||||
this.banned = {};
|
||||
|
||||
this.maxBuckets = 20;
|
||||
this.maxEntries = 50;
|
||||
this.maxAddresses = this.maxBuckets * this.maxEntries;
|
||||
|
||||
this.horizonDays = 30;
|
||||
this.retries = 3;
|
||||
this.minFailDays = 7;
|
||||
this.maxFailures = 10;
|
||||
this.maxRefs = 8;
|
||||
|
||||
this.filename = null;
|
||||
this.flushInterval = 120000;
|
||||
this.timer = null;
|
||||
|
||||
this._initOptions(options);
|
||||
this._init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of days before considering
|
||||
* an address stale.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
HostList.HORIZON_DAYS = 30;
|
||||
|
||||
/**
|
||||
* Number of retries (without success)
|
||||
* before considering an address stale.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
HostList.RETRIES = 3;
|
||||
|
||||
/**
|
||||
* Number of days after reaching
|
||||
* MAX_FAILURES to consider an
|
||||
* address stale.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
HostList.MIN_FAIL_DAYS = 7;
|
||||
|
||||
/**
|
||||
* Maximum number of failures
|
||||
* allowed before considering
|
||||
* an address stale.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
HostList.MAX_FAILURES = 10;
|
||||
|
||||
/**
|
||||
* Maximum number of references
|
||||
* in fresh buckets.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
HostList.MAX_REFS = 8;
|
||||
|
||||
/**
|
||||
* Serialization version.
|
||||
* @const {Number}
|
||||
@ -78,58 +111,19 @@ function HostList(options) {
|
||||
HostList.VERSION = 0;
|
||||
|
||||
/**
|
||||
* Initialize options.
|
||||
* @private
|
||||
* Local address scores.
|
||||
* @enum {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
HostList.prototype._initOptions = function initOptions(options) {
|
||||
if (!options)
|
||||
return;
|
||||
|
||||
if (options.network != null) {
|
||||
this.network = Network.get(options.network);
|
||||
this.rawSeeds = this.network.seeds;
|
||||
}
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.address != null) {
|
||||
assert(options.address instanceof NetAddress);
|
||||
this.address = options.address;
|
||||
}
|
||||
|
||||
if (options.resolve != null) {
|
||||
assert(typeof options.resolve === 'function');
|
||||
this.resolve = options.resolve;
|
||||
}
|
||||
|
||||
if (options.banTime != null) {
|
||||
assert(options.banTime >= 0);
|
||||
this.banTime = options.banTime;
|
||||
}
|
||||
|
||||
if (options.seeds) {
|
||||
assert(Array.isArray(options.seeds));
|
||||
this.rawSeeds = options.seeds;
|
||||
}
|
||||
|
||||
if (options.nodes) {
|
||||
assert(Array.isArray(options.nodes));
|
||||
this.rawNodes = options.nodes;
|
||||
}
|
||||
|
||||
if (options.hostLocation != null) {
|
||||
assert(typeof options.hostLocation === 'string');
|
||||
this.filename = options.hostLocation;
|
||||
}
|
||||
|
||||
if (options.flushInterval != null) {
|
||||
assert(options.flushInterval >= 0);
|
||||
this.flushInterval = options.flushInterval;
|
||||
}
|
||||
HostList.scores = {
|
||||
NONE: 0,
|
||||
IF: 1,
|
||||
BIND: 2,
|
||||
UPNP: 3,
|
||||
HTTP: 3,
|
||||
MANUAL: 4,
|
||||
MAX: 5
|
||||
};
|
||||
|
||||
/**
|
||||
@ -138,16 +132,28 @@ HostList.prototype._initOptions = function initOptions(options) {
|
||||
*/
|
||||
|
||||
HostList.prototype._init = function init() {
|
||||
var i;
|
||||
var options = this.options;
|
||||
var scores = HostList.scores;
|
||||
var hosts = IP.getPublic();
|
||||
var port = this.address.port;
|
||||
var i, host;
|
||||
|
||||
for (i = 0; i < this.maxBuckets; i++)
|
||||
for (i = 0; i < this.options.maxBuckets; i++)
|
||||
this.fresh.push(new Map());
|
||||
|
||||
for (i = 0; i < this.maxBuckets; i++)
|
||||
for (i = 0; i < this.options.maxBuckets; i++)
|
||||
this.used.push(new List());
|
||||
|
||||
this.setSeeds(this.rawSeeds);
|
||||
this.setNodes(this.rawNodes);
|
||||
this.setSeeds(options.seeds);
|
||||
this.setNodes(options.nodes);
|
||||
|
||||
this.pushLocal(this.address, scores.MANUAL);
|
||||
this.addLocal(options.host, options.port, scores.BIND);
|
||||
|
||||
for (i = 0; i < hosts.length; i++) {
|
||||
host = hosts[i];
|
||||
this.addLocal(host, port, scores.IF);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -158,14 +164,17 @@ HostList.prototype._init = function init() {
|
||||
|
||||
HostList.prototype.open = co(function* open() {
|
||||
try {
|
||||
yield this.read();
|
||||
yield this.loadFile();
|
||||
} catch (e) {
|
||||
if (this.logger) {
|
||||
this.logger.warning('Hosts deserialization failed.');
|
||||
this.logger.error(e);
|
||||
}
|
||||
this.logger.warning('Hosts deserialization failed.');
|
||||
this.logger.error(e);
|
||||
}
|
||||
|
||||
if (this.size() === 0)
|
||||
this.injectSeeds();
|
||||
|
||||
yield this.discoverNodes();
|
||||
|
||||
this.start();
|
||||
});
|
||||
|
||||
@ -178,6 +187,7 @@ HostList.prototype.open = co(function* open() {
|
||||
HostList.prototype.close = co(function* close() {
|
||||
this.stop();
|
||||
yield this.flush();
|
||||
this.reset();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -185,11 +195,11 @@ HostList.prototype.close = co(function* close() {
|
||||
*/
|
||||
|
||||
HostList.prototype.start = function start() {
|
||||
if (!this.filename)
|
||||
if (!this.options.hostLocation)
|
||||
return;
|
||||
|
||||
assert(!this.timer);
|
||||
this.timer = setInterval(this.flush.bind(this), this.flushInterval);
|
||||
assert(this.timer == null);
|
||||
this.timer = co.setInterval(this.flush, this.options.flushInterval, this);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -197,28 +207,56 @@ HostList.prototype.start = function start() {
|
||||
*/
|
||||
|
||||
HostList.prototype.stop = function stop() {
|
||||
if (!this.filename)
|
||||
if (!this.options.hostLocation)
|
||||
return;
|
||||
|
||||
assert(this.timer != null);
|
||||
clearInterval(this.timer);
|
||||
co.clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read and initialie from hosts file.
|
||||
* Read and initialize from hosts file.
|
||||
* @method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
HostList.prototype.read = co(function* read() {
|
||||
HostList.prototype.injectSeeds = function injectSeeds() {
|
||||
var nodes = seeds.get(this.network.type);
|
||||
var i, node, addr;
|
||||
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
node = nodes[i];
|
||||
addr = NetAddress.fromHostname(node, this.network);
|
||||
|
||||
if (!addr.isRoutable())
|
||||
continue;
|
||||
|
||||
if (!this.options.onion && addr.isOnion())
|
||||
continue;
|
||||
|
||||
if (addr.port === 0)
|
||||
continue;
|
||||
|
||||
this.add(addr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Read and initialize from hosts file.
|
||||
* @method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
HostList.prototype.loadFile = co(function* loadFile() {
|
||||
var filename = this.options.hostLocation;
|
||||
var data, json;
|
||||
|
||||
if (!this.filename)
|
||||
if (!filename)
|
||||
return;
|
||||
|
||||
try {
|
||||
data = yield readFile(this.filename, 'utf8');
|
||||
data = yield readFile(filename, 'utf8');
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT')
|
||||
return;
|
||||
@ -237,24 +275,22 @@ HostList.prototype.read = co(function* read() {
|
||||
*/
|
||||
|
||||
HostList.prototype.flush = co(function* flush() {
|
||||
var filename = this.options.hostLocation;
|
||||
var json, data;
|
||||
|
||||
if (!this.filename)
|
||||
if (!filename)
|
||||
return;
|
||||
|
||||
if (this.logger)
|
||||
this.logger.debug('Writing hosts to %s.', this.filename);
|
||||
this.logger.debug('Writing hosts to %s.', filename);
|
||||
|
||||
json = this.toJSON();
|
||||
data = JSON.stringify(json);
|
||||
|
||||
try {
|
||||
yield writeFile(this.filename, data, 'utf8');
|
||||
yield writeFile(filename, data, 'utf8');
|
||||
} catch (e) {
|
||||
if (this.logger) {
|
||||
this.logger.warning('Writing hosts failed.');
|
||||
this.logger.error(e);
|
||||
}
|
||||
this.logger.warning('Writing hosts failed.');
|
||||
this.logger.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
@ -273,7 +309,8 @@ HostList.prototype.size = function size() {
|
||||
*/
|
||||
|
||||
HostList.prototype.isFull = function isFull() {
|
||||
return this.size() >= this.maxAddresses;
|
||||
var max = this.options.maxBuckets * this.options.maxEntries;
|
||||
return this.size() >= max;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -297,6 +334,8 @@ HostList.prototype.reset = function reset() {
|
||||
|
||||
this.totalFresh = 0;
|
||||
this.totalUsed = 0;
|
||||
|
||||
this.nodes.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -337,7 +376,7 @@ HostList.prototype.isBanned = function isBanned(host) {
|
||||
if (time == null)
|
||||
return false;
|
||||
|
||||
if (util.now() > time + this.banTime) {
|
||||
if (util.now() > time + this.options.banTime) {
|
||||
delete this.banned[host];
|
||||
return false;
|
||||
}
|
||||
@ -477,10 +516,10 @@ HostList.prototype.add = function add(addr, src) {
|
||||
|
||||
// Do not update if the max
|
||||
// reference count is reached.
|
||||
if (entry.refCount === this.maxRefs)
|
||||
if (entry.refCount === HostList.MAX_REFS)
|
||||
return false;
|
||||
|
||||
assert(entry.refCount < this.maxRefs);
|
||||
assert(entry.refCount < HostList.MAX_REFS);
|
||||
|
||||
// Stochastic test: previous refCount
|
||||
// N: 2^N times harder to increase it.
|
||||
@ -506,7 +545,7 @@ HostList.prototype.add = function add(addr, src) {
|
||||
if (bucket.has(entry.key()))
|
||||
return false;
|
||||
|
||||
if (bucket.size >= this.maxEntries)
|
||||
if (bucket.size >= this.options.maxEntries)
|
||||
this.evictFresh(bucket);
|
||||
|
||||
bucket.set(entry.key(), entry);
|
||||
@ -579,14 +618,14 @@ HostList.prototype.isStale = function isStale(entry) {
|
||||
if (entry.addr.ts === 0)
|
||||
return true;
|
||||
|
||||
if (now - entry.addr.ts > this.horizonDays * 24 * 60 * 60)
|
||||
if (now - entry.addr.ts > HostList.HORIZON_DAYS * 24 * 60 * 60)
|
||||
return true;
|
||||
|
||||
if (entry.lastSuccess === 0 && entry.attempts >= this.retries)
|
||||
if (entry.lastSuccess === 0 && entry.attempts >= HostList.RETRIES)
|
||||
return true;
|
||||
|
||||
if (now - entry.lastSuccess > this.minFailDays * 24 * 60 * 60) {
|
||||
if (entry.attempts >= this.maxFailures)
|
||||
if (now - entry.lastSuccess > HostList.MIN_FAIL_DAYS * 24 * 60 * 60) {
|
||||
if (entry.attempts >= HostList.MAX_FAILURES)
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -713,7 +752,7 @@ HostList.prototype.markAck = function markAck(hostname, services) {
|
||||
// Find room in used bucket.
|
||||
bucket = this.usedBucket(entry);
|
||||
|
||||
if (bucket.size < this.maxEntries) {
|
||||
if (bucket.size < this.options.maxEntries) {
|
||||
entry.used = true;
|
||||
bucket.push(entry);
|
||||
this.totalUsed++;
|
||||
@ -725,7 +764,7 @@ HostList.prototype.markAck = function markAck(hostname, services) {
|
||||
fresh = this.freshBucket(evicted);
|
||||
|
||||
// Move to entry's old bucket if no room.
|
||||
if (fresh.size >= this.maxEntries)
|
||||
if (fresh.size >= this.options.maxEntries)
|
||||
fresh = old;
|
||||
|
||||
// Swap to evicted's used bucket.
|
||||
@ -855,27 +894,131 @@ HostList.prototype.setNodes = function setNodes(nodes) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Discover hosts from seeds and nodes.
|
||||
* Add a local address.
|
||||
* @param {String} host
|
||||
* @param {Number} port
|
||||
* @param {Number} score
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
HostList.prototype.addLocal = function addLocal(host, port, score) {
|
||||
var addr = NetAddress.fromHost(host, port, this.network);
|
||||
addr.services = this.options.services;
|
||||
return this.pushLocal(addr, score);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a local address.
|
||||
* @param {NetAddress} addr
|
||||
* @param {Number} score
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
HostList.prototype.pushLocal = function pushLocal(addr, score) {
|
||||
var local;
|
||||
|
||||
if (!addr.isRoutable())
|
||||
return false;
|
||||
|
||||
if (this.local.has(addr.hostname))
|
||||
return false;
|
||||
|
||||
local = new LocalAddress(addr, score);
|
||||
|
||||
this.local.set(addr.hostname, local);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get local address based on reachability.
|
||||
* @param {NetAddress?} src
|
||||
* @returns {NetAddress}
|
||||
*/
|
||||
|
||||
HostList.prototype.getLocal = function getLocal(src) {
|
||||
var keys = this.local.keys();
|
||||
var bestReach = -1;
|
||||
var bestScore = -1;
|
||||
var bestDest = null;
|
||||
var i, key, dest, reach;
|
||||
|
||||
if (!src)
|
||||
src = this.address;
|
||||
|
||||
if (keys.length === 0)
|
||||
return null;
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
key = keys[i];
|
||||
dest = this.local.get(key);
|
||||
reach = src.getReachability(dest.addr);
|
||||
|
||||
if (reach < bestReach)
|
||||
continue;
|
||||
|
||||
if (reach > bestReach || dest.score > bestScore) {
|
||||
bestReach = reach;
|
||||
bestScore = dest.score;
|
||||
bestDest = dest.addr;
|
||||
}
|
||||
}
|
||||
|
||||
bestDest.ts = this.network.now();
|
||||
|
||||
return bestDest;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark local address as seen during a handshake.
|
||||
* @param {NetAddress} addr
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
HostList.prototype.markLocal = function markLocal(addr) {
|
||||
var local = this.local.get(addr.hostname);
|
||||
|
||||
if (!local)
|
||||
return false;
|
||||
|
||||
local.score++;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Discover hosts from seeds.
|
||||
* @method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
HostList.prototype.discover = co(function* discover() {
|
||||
HostList.prototype.discoverSeeds = co(function* discoverSeeds() {
|
||||
var jobs = [];
|
||||
var i, node, seed;
|
||||
var i, seed;
|
||||
|
||||
for (i = 0; i < this.dnsSeeds.length; i++) {
|
||||
seed = this.dnsSeeds[i];
|
||||
jobs.push(this.populateSeed(seed));
|
||||
}
|
||||
|
||||
yield Promise.all(jobs);
|
||||
});
|
||||
|
||||
/**
|
||||
* Discover hosts from nodes.
|
||||
* @method
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
HostList.prototype.discoverNodes = co(function* discoverNodes() {
|
||||
var jobs = [];
|
||||
var i, node;
|
||||
|
||||
for (i = 0; i < this.dnsNodes.length; i++) {
|
||||
node = this.dnsNodes[i];
|
||||
jobs.push(this.populateNode(node));
|
||||
}
|
||||
|
||||
this.dnsNodes.length = 0;
|
||||
|
||||
yield Promise.all(jobs);
|
||||
});
|
||||
|
||||
@ -926,14 +1069,12 @@ HostList.prototype.populate = co(function* populate(target) {
|
||||
|
||||
assert(target.type === IP.types.DNS, 'Resolved host passed.');
|
||||
|
||||
if (this.logger)
|
||||
this.logger.info('Resolving host: %s.', target.host);
|
||||
this.logger.info('Resolving host: %s.', target.host);
|
||||
|
||||
try {
|
||||
hosts = yield this.resolve(target.host);
|
||||
} catch (e) {
|
||||
if (this.logger)
|
||||
this.logger.error(e);
|
||||
this.logger.error(e);
|
||||
return addrs;
|
||||
}
|
||||
|
||||
@ -1027,6 +1168,8 @@ HostList.prototype.fromJSON = function fromJSON(json) {
|
||||
}
|
||||
|
||||
assert(Array.isArray(json.fresh));
|
||||
assert(json.fresh.length <= this.options.maxBuckets,
|
||||
'Buckets mismatch.');
|
||||
|
||||
for (i = 0; i < json.fresh.length; i++) {
|
||||
keys = json.fresh[i];
|
||||
@ -1042,7 +1185,7 @@ HostList.prototype.fromJSON = function fromJSON(json) {
|
||||
bucket.set(key, entry);
|
||||
}
|
||||
|
||||
assert(bucket.size <= this.maxEntries,
|
||||
assert(bucket.size <= this.options.maxEntries,
|
||||
'Bucket size mismatch.');
|
||||
|
||||
fresh.push(bucket);
|
||||
@ -1052,6 +1195,8 @@ HostList.prototype.fromJSON = function fromJSON(json) {
|
||||
'Buckets mismatch.');
|
||||
|
||||
assert(Array.isArray(json.used));
|
||||
assert(json.used.length <= this.options.maxBuckets,
|
||||
'Buckets mismatch.');
|
||||
|
||||
for (i = 0; i < json.used.length; i++) {
|
||||
keys = json.used[i];
|
||||
@ -1068,7 +1213,7 @@ HostList.prototype.fromJSON = function fromJSON(json) {
|
||||
bucket.push(entry);
|
||||
}
|
||||
|
||||
assert(bucket.size <= this.maxEntries,
|
||||
assert(bucket.size <= this.options.maxEntries,
|
||||
'Bucket size mismatch.');
|
||||
|
||||
used.push(bucket);
|
||||
@ -1285,6 +1430,161 @@ HostEntry.fromJSON = function fromJSON(json, network) {
|
||||
return new HostEntry().fromJSON(json, network);
|
||||
};
|
||||
|
||||
/**
|
||||
* LocalAddress
|
||||
* @alias module:net.LocalAddress
|
||||
* @constructor
|
||||
* @param {NetAddress} addr
|
||||
* @param {Number?} score
|
||||
*/
|
||||
|
||||
function LocalAddress(addr, score) {
|
||||
this.addr = addr;
|
||||
this.score = score || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Host List Options
|
||||
* @alias module:net.HostListOptions
|
||||
* @constructor
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
function HostListOptions(options) {
|
||||
if (!(this instanceof HostListOptions))
|
||||
return new HostListOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = Logger.global;
|
||||
this.resolve = dns.lookup;
|
||||
this.host = '0.0.0.0';
|
||||
this.port = this.network.port;
|
||||
this.services = common.LOCAL_SERVICES;
|
||||
this.onion = false;
|
||||
this.banTime = common.BAN_TIME;
|
||||
|
||||
this.address = new NetAddress();
|
||||
this.address.services = this.services;
|
||||
this.address.ts = this.network.now();
|
||||
|
||||
this.seeds = this.network.seeds;
|
||||
this.nodes = [];
|
||||
|
||||
this.maxBuckets = 20;
|
||||
this.maxEntries = 50;
|
||||
|
||||
this.hostLocation = null;
|
||||
this.flushInterval = 120000;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from options.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
HostListOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
var raw;
|
||||
|
||||
assert(options, 'Options are required.');
|
||||
|
||||
if (options.network != null) {
|
||||
this.network = Network.get(options.network);
|
||||
this.seeds = this.network.seeds;
|
||||
this.address.port = this.network.port;
|
||||
this.port = this.network.port;
|
||||
}
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.resolve != null) {
|
||||
assert(typeof options.resolve === 'function');
|
||||
this.resolve = options.resolve;
|
||||
}
|
||||
|
||||
if (options.banTime != null) {
|
||||
assert(options.banTime >= 0);
|
||||
this.banTime = options.banTime;
|
||||
}
|
||||
|
||||
if (options.seeds) {
|
||||
assert(Array.isArray(options.seeds));
|
||||
this.seeds = options.seeds;
|
||||
}
|
||||
|
||||
if (options.nodes) {
|
||||
assert(Array.isArray(options.nodes));
|
||||
this.nodes = options.nodes;
|
||||
}
|
||||
|
||||
if (options.host != null) {
|
||||
assert(typeof options.host === 'string');
|
||||
raw = IP.toBuffer(options.host);
|
||||
this.host = IP.toString(raw);
|
||||
if (IP.isRoutable(raw))
|
||||
this.address.setHost(this.host);
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
assert(typeof options.port === 'number');
|
||||
assert(options.port > 0 && options.port <= 0xffff);
|
||||
this.port = options.port;
|
||||
this.address.setPort(this.port);
|
||||
}
|
||||
|
||||
if (options.publicHost != null) {
|
||||
assert(typeof options.publicHost === 'string');
|
||||
this.address.setHost(options.publicHost);
|
||||
}
|
||||
|
||||
if (options.publicPort != null) {
|
||||
assert(typeof options.publicPort === 'number');
|
||||
assert(options.publicPort > 0 && options.publicPort <= 0xffff);
|
||||
this.address.setPort(options.publicPort);
|
||||
}
|
||||
|
||||
if (options.services != null) {
|
||||
assert(typeof options.services === 'number');
|
||||
this.services = options.services;
|
||||
}
|
||||
|
||||
if (options.onion != null) {
|
||||
assert(typeof options.onion === 'boolean');
|
||||
this.onion = options.onion;
|
||||
}
|
||||
|
||||
if (options.maxBuckets != null) {
|
||||
assert(typeof options.maxBuckets === 'number');
|
||||
this.maxBuckets = options.maxBuckets;
|
||||
}
|
||||
|
||||
if (options.maxEntries != null) {
|
||||
assert(typeof options.maxEntries === 'number');
|
||||
this.maxEntries = options.maxEntries;
|
||||
}
|
||||
|
||||
if (options.hostLocation != null) {
|
||||
assert(typeof options.hostLocation === 'string');
|
||||
this.hostLocation = options.hostLocation;
|
||||
}
|
||||
|
||||
if (options.flushInterval != null) {
|
||||
assert(options.flushInterval >= 0);
|
||||
this.flushInterval = options.flushInterval;
|
||||
}
|
||||
|
||||
this.address.ts = this.network.now();
|
||||
this.address.services = this.services;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
@ -169,8 +169,8 @@ function VersionPacket(options) {
|
||||
this.version = common.PROTOCOL_VERSION;
|
||||
this.services = common.LOCAL_SERVICES;
|
||||
this.ts = util.now();
|
||||
this.recv = new NetAddress();
|
||||
this.from = new NetAddress();
|
||||
this.remote = new NetAddress();
|
||||
this.local = new NetAddress();
|
||||
this.nonce = encoding.ZERO_U64;
|
||||
this.agent = common.USER_AGENT;
|
||||
this.height = 0;
|
||||
@ -201,11 +201,11 @@ VersionPacket.prototype.fromOptions = function fromOptions(options) {
|
||||
if (options.ts != null)
|
||||
this.ts = options.ts;
|
||||
|
||||
if (options.recv)
|
||||
this.recv.fromOptions(options.recv);
|
||||
if (options.remote)
|
||||
this.remote.fromOptions(options.remote);
|
||||
|
||||
if (options.from)
|
||||
this.from.fromOptions(options.from);
|
||||
if (options.local)
|
||||
this.local.fromOptions(options.local);
|
||||
|
||||
if (options.nonce)
|
||||
this.nonce = options.nonce;
|
||||
@ -240,8 +240,8 @@ VersionPacket.fromOptions = function fromOptions(options) {
|
||||
VersionPacket.prototype.getSize = function getSize() {
|
||||
var size = 0;
|
||||
size += 20;
|
||||
size += this.recv.getSize(false);
|
||||
size += this.from.getSize(false);
|
||||
size += this.remote.getSize(false);
|
||||
size += this.local.getSize(false);
|
||||
size += 8;
|
||||
size += encoding.sizeVarString(this.agent, 'ascii');
|
||||
size += 5;
|
||||
@ -258,8 +258,8 @@ VersionPacket.prototype.toWriter = function toWriter(bw) {
|
||||
bw.writeU32(this.services);
|
||||
bw.writeU32(0);
|
||||
bw.write64(this.ts);
|
||||
this.recv.toWriter(bw, false);
|
||||
this.from.toWriter(bw, false);
|
||||
this.remote.toWriter(bw, false);
|
||||
this.local.toWriter(bw, false);
|
||||
bw.writeBytes(this.nonce);
|
||||
bw.writeVarString(this.agent, 'ascii');
|
||||
bw.write32(this.height);
|
||||
@ -292,10 +292,10 @@ VersionPacket.prototype.fromReader = function fromReader(br) {
|
||||
br.readU32();
|
||||
|
||||
this.ts = br.read53();
|
||||
this.recv.fromReader(br, false);
|
||||
this.remote.fromReader(br, false);
|
||||
|
||||
if (br.left() > 0) {
|
||||
this.from.fromReader(br, false);
|
||||
this.local.fromReader(br, false);
|
||||
this.nonce = br.readBytes(8);
|
||||
}
|
||||
|
||||
|
||||
@ -84,6 +84,7 @@ function Peer(options) {
|
||||
this.outbound = false;
|
||||
this.loader = false;
|
||||
this.address = new NetAddress();
|
||||
this.local = new NetAddress();
|
||||
this.connected = false;
|
||||
this.destroyed = false;
|
||||
this.ack = false;
|
||||
@ -979,8 +980,9 @@ Peer.prototype.sendVersion = function sendVersion() {
|
||||
packet.version = this.options.version;
|
||||
packet.services = this.options.services;
|
||||
packet.ts = this.network.now();
|
||||
packet.recv = this.address;
|
||||
packet.from = this.options.address;
|
||||
packet.remote = this.address;
|
||||
packet.local.setNull();
|
||||
packet.local.services = this.options.services;
|
||||
packet.nonce = this.options.createNonce(this.hostname());
|
||||
packet.agent = this.options.agent;
|
||||
packet.height = this.options.getHeight();
|
||||
@ -1703,6 +1705,7 @@ Peer.prototype.handleVersion = co(function* handleVersion(packet) {
|
||||
this.height = packet.height;
|
||||
this.agent = packet.agent;
|
||||
this.noRelay = packet.noRelay;
|
||||
this.local = packet.remote;
|
||||
|
||||
if (!this.network.selfConnect) {
|
||||
if (this.options.hasNonce(packet.nonce))
|
||||
@ -2275,7 +2278,6 @@ function PeerOptions(options) {
|
||||
this.createSocket = tcp.createSocket;
|
||||
this.version = common.PROTOCOL_VERSION;
|
||||
this.services = common.LOCAL_SERVICES;
|
||||
this.address = new NetAddress();
|
||||
this.agent = common.USER_AGENT;
|
||||
this.noRelay = false;
|
||||
this.spv = false;
|
||||
@ -2327,11 +2329,6 @@ PeerOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
this.services = options.services;
|
||||
}
|
||||
|
||||
if (options.address != null) {
|
||||
assert(typeof options.address === 'object');
|
||||
this.address = options.address;
|
||||
}
|
||||
|
||||
if (options.agent != null) {
|
||||
assert(typeof options.agent === 'string');
|
||||
this.agent = options.agent;
|
||||
|
||||
260
lib/net/pool.js
260
lib/net/pool.js
@ -15,7 +15,6 @@ var IP = require('../utils/ip');
|
||||
var co = require('../utils/co');
|
||||
var common = require('./common');
|
||||
var chainCommon = require('../blockchain/common');
|
||||
var NetAddress = require('../primitives/netaddress');
|
||||
var Address = require('../primitives/address');
|
||||
var BIP150 = require('./bip150');
|
||||
var BIP151 = require('./bip151');
|
||||
@ -30,12 +29,14 @@ var List = require('../utils/list');
|
||||
var tcp = require('./tcp');
|
||||
var dns = require('./dns');
|
||||
var HostList = require('./hostlist');
|
||||
var UPNP = require('./upnp');
|
||||
var InvItem = require('../primitives/invitem');
|
||||
var Map = require('../utils/map');
|
||||
var packets = require('./packets');
|
||||
var services = common.services;
|
||||
var invTypes = InvItem.types;
|
||||
var packetTypes = packets.types;
|
||||
var scores = HostList.scores;
|
||||
|
||||
/**
|
||||
* A pool of peers for handling all network activity.
|
||||
@ -85,7 +86,6 @@ function Pool(options) {
|
||||
this.mempool = this.options.mempool;
|
||||
this.server = this.options.createServer();
|
||||
this.nonces = this.options.nonces;
|
||||
this.address = this.options.address;
|
||||
|
||||
this.locker = new Lock(true);
|
||||
this.connected = false;
|
||||
@ -127,7 +127,15 @@ util.inherits(Pool, AsyncObject);
|
||||
* @default
|
||||
*/
|
||||
|
||||
Pool.MAX_HEADER_FAILS = 500;
|
||||
Pool.MAX_HEADER_FAILS = 1000;
|
||||
|
||||
/**
|
||||
* Discovery interval for UPNP and DNS seeds.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
Pool.DISCOVERY_INTERVAL = 120000;
|
||||
|
||||
/**
|
||||
* Initialize the pool.
|
||||
@ -286,38 +294,24 @@ Pool.prototype.connect = co(function* connect() {
|
||||
*/
|
||||
|
||||
Pool.prototype._connect = co(function* connect() {
|
||||
var ip;
|
||||
|
||||
assert(this.loaded, 'Pool is not loaded.');
|
||||
|
||||
if (this.connected)
|
||||
return;
|
||||
|
||||
if (this.address.isNull() && !this.options.proxy) {
|
||||
try {
|
||||
ip = yield this.getIP();
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
}
|
||||
if (ip) {
|
||||
this.address.setHost(ip);
|
||||
this.logger.info('External IP found: %s.', ip);
|
||||
}
|
||||
}
|
||||
yield this.hosts.open();
|
||||
yield this.authdb.open();
|
||||
|
||||
yield this.authdb.discover();
|
||||
|
||||
yield this.hosts.discover();
|
||||
|
||||
if (this.hosts.size() === 0)
|
||||
throw new Error('No hosts available. Do you have an internet connection?');
|
||||
|
||||
this.logger.info('Resolved %d hosts from DNS seeds.', this.hosts.size());
|
||||
yield this.discoverGateway();
|
||||
yield this.discoverExternal();
|
||||
yield this.discoverSeeds();
|
||||
|
||||
this.fillOutbound();
|
||||
|
||||
yield this.listen();
|
||||
|
||||
this.startTimer();
|
||||
|
||||
this.connected = true;
|
||||
});
|
||||
|
||||
@ -380,6 +374,11 @@ Pool.prototype._disconnect = co(function* disconnect() {
|
||||
this.headerChain.reset();
|
||||
this.headerNext = null;
|
||||
|
||||
this.stopTimer();
|
||||
|
||||
yield this.authdb.close();
|
||||
yield this.hosts.close();
|
||||
|
||||
yield this.unlisten();
|
||||
|
||||
this.disconnecting = false;
|
||||
@ -423,6 +422,160 @@ Pool.prototype.unlisten = co(function* unlisten() {
|
||||
yield this.server.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Start discovery timer.
|
||||
* @private
|
||||
*/
|
||||
|
||||
Pool.prototype.startTimer = function startTimer() {
|
||||
assert(this.timer == null, 'Timer already started.');
|
||||
this.timer = co.setInterval(this.discover, Pool.DISCOVERY_INTERVAL, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop discovery timer.
|
||||
* @private
|
||||
*/
|
||||
|
||||
Pool.prototype.stopTimer = function stopTimer() {
|
||||
assert(this.timer != null, 'Timer already stopped.');
|
||||
co.clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rediscover seeds and internet gateway.
|
||||
* Attempt to add port mapping once again.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Pool.prototype.discover = co(function* discover() {
|
||||
yield this.discoverGateway();
|
||||
yield this.discoverSeeds(true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Attempt to add port mapping (i.e.
|
||||
* remote:8333->local:8333) via UPNP.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Pool.prototype.discoverGateway = co(function* discoverGateway() {
|
||||
var src = this.options.publicPort;
|
||||
var dest = this.options.port;
|
||||
var wan, host;
|
||||
|
||||
// Pointless if we're not listening.
|
||||
if (!this.options.listen)
|
||||
return;
|
||||
|
||||
// UPNP is always optional, since
|
||||
// it's likely to not work anyway.
|
||||
if (!this.options.upnp)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.logger.debug('Discovering internet gateway (upnp).');
|
||||
wan = yield UPNP.discover();
|
||||
} catch (e) {
|
||||
this.logger.debug('UPNP error:');
|
||||
this.logger.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
host = yield wan.getExternalIP();
|
||||
} catch (e) {
|
||||
this.logger.debug('Could not find external IP.');
|
||||
this.logger.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.hosts.addLocal(host, src, scores.UPNP))
|
||||
this.logger.info('External IP found (upnp): %s.', host);
|
||||
|
||||
this.logger.debug(
|
||||
'Adding port mapping %d->%d.',
|
||||
src, dest);
|
||||
|
||||
try {
|
||||
yield wan.addPortMapping(host, src, dest);
|
||||
} catch (e) {
|
||||
this.logger.debug('Could not add port mapping.');
|
||||
this.logger.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Attempt to resolve DNS seeds if necessary.
|
||||
* @param {Boolean} checkPeers
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Pool.prototype.discoverSeeds = co(function* discoverSeeds(checkPeers) {
|
||||
var total = 0;
|
||||
var max = Math.min(2, this.options.maxOutbound);
|
||||
var size = this.hosts.size();
|
||||
var peer;
|
||||
|
||||
for (peer = this.peers.head(); peer; peer = peer.next) {
|
||||
if (peer.handshake) {
|
||||
if (++total > max)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (size === 0 || (checkPeers && total < max)) {
|
||||
this.logger.warning('Could not find enough peers.');
|
||||
this.logger.warning('Hitting DNS seeds...');
|
||||
|
||||
yield this.hosts.discoverSeeds();
|
||||
|
||||
this.logger.info(
|
||||
'Resolved %d hosts from DNS seeds.',
|
||||
this.hosts.size() - size);
|
||||
|
||||
this.refill();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Attempt to discover external IP via HTTP.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Pool.prototype.discoverExternal = co(function* discoverExternal() {
|
||||
var port = this.options.publicPort;
|
||||
var host;
|
||||
|
||||
// Pointless if we're not listening.
|
||||
if (!this.options.listen)
|
||||
return;
|
||||
|
||||
// Never hit an HTTP 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;
|
||||
|
||||
try {
|
||||
host = yield this.getIP();
|
||||
} catch (e) {
|
||||
this.logger.debug('Could not find external IP.');
|
||||
this.logger.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hosts.addLocal(host, port, scores.HTTP))
|
||||
this.logger.info('External IP found (http): %s.', host);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle incoming connection.
|
||||
* @private
|
||||
@ -1118,11 +1271,13 @@ Pool.prototype.handleConnect = co(function* handleConnect(peer) {
|
||||
*/
|
||||
|
||||
Pool.prototype.handleOpen = co(function* handleOpen(peer) {
|
||||
var addr;
|
||||
|
||||
// Advertise our address.
|
||||
if (!this.address.isNull()
|
||||
&& !this.options.selfish
|
||||
&& this.options.listen) {
|
||||
peer.send(new packets.AddrPacket([this.address]));
|
||||
if (!this.options.selfish && this.options.listen) {
|
||||
addr = this.hosts.getLocal(peer.address);
|
||||
if (addr)
|
||||
peer.send(new packets.AddrPacket([addr]));
|
||||
}
|
||||
|
||||
// We want compact blocks!
|
||||
@ -1239,6 +1394,9 @@ Pool.prototype.handleVersion = co(function* handleVersion(peer, packet) {
|
||||
|
||||
this.network.time.add(peer.hostname(), packet.ts);
|
||||
this.nonces.remove(peer.hostname());
|
||||
|
||||
if (!peer.outbound && packet.remote.isRoutable())
|
||||
this.hosts.markLocal(packet.remote);
|
||||
});
|
||||
|
||||
/**
|
||||
@ -2492,7 +2650,7 @@ Pool.prototype.handleSendCmpct = co(function* handleSendCmpct(peer, packet) {
|
||||
Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
|
||||
var block = packet.block;
|
||||
var hash = block.hash('hex');
|
||||
var witness = peer.hasWitness();
|
||||
var witness = peer.compactWitness;
|
||||
var flags = chainCommon.flags.VERIFY_BODY;
|
||||
var result;
|
||||
|
||||
@ -2507,7 +2665,7 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!peer.hasCompactSupport()) {
|
||||
if (!peer.hasCompactSupport() && !peer.hasCompact()) {
|
||||
this.logger.info(
|
||||
'Peer sent unsolicited cmpctblock (%s).',
|
||||
peer.hostname());
|
||||
@ -3394,8 +3552,6 @@ function PoolOptions(options) {
|
||||
this.mempool = null;
|
||||
|
||||
this.nonces = new NonceList();
|
||||
this.address = new NetAddress();
|
||||
this.address.port = this.network.port;
|
||||
|
||||
this.checkpoints = true;
|
||||
this.spv = false;
|
||||
@ -3405,6 +3561,8 @@ function PoolOptions(options) {
|
||||
this.noRelay = false;
|
||||
this.host = '0.0.0.0';
|
||||
this.port = this.network.port;
|
||||
this.publicHost = '0.0.0.0';
|
||||
this.publicPort = this.network.port;
|
||||
this.maxOutbound = 8;
|
||||
this.maxInbound = 8;
|
||||
this.createSocket = this._createSocket.bind(this);
|
||||
@ -3412,6 +3570,7 @@ function PoolOptions(options) {
|
||||
this.resolve = this._resolve.bind(this);
|
||||
this.proxy = null;
|
||||
this.onion = false;
|
||||
this.upnp = false;
|
||||
this.selfish = false;
|
||||
this.version = common.PROTOCOL_VERSION;
|
||||
this.agent = common.USER_AGENT;
|
||||
@ -3441,6 +3600,8 @@ function PoolOptions(options) {
|
||||
*/
|
||||
|
||||
PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
var raw;
|
||||
|
||||
assert(options, 'Pool requires options.');
|
||||
assert(options.chain && typeof options.chain === 'object',
|
||||
'Pool options require a blockchain.');
|
||||
@ -3451,7 +3612,8 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
|
||||
this.port = this.network.port;
|
||||
this.seeds = this.network.seeds;
|
||||
this.address.port = this.network.port;
|
||||
this.port = this.network.port;
|
||||
this.publicPort = this.network.port;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
@ -3501,30 +3663,33 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
|
||||
if (options.host != null) {
|
||||
assert(typeof options.host === 'string');
|
||||
this.host = options.host;
|
||||
this.address.setHost(this.host);
|
||||
if (!this.address.isRoutable())
|
||||
this.address.setNull();
|
||||
raw = IP.toBuffer(options.host);
|
||||
this.host = IP.toString(raw);
|
||||
if (IP.isRoutable(raw))
|
||||
this.publicHost = this.host;
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
assert(typeof options.port === 'number');
|
||||
assert(options.port > 0 && options.port <= 0xffff);
|
||||
this.port = options.port;
|
||||
this.address.setPort(this.port);
|
||||
this.publicPort = options.port;
|
||||
}
|
||||
|
||||
if (options.publicHost != null) {
|
||||
assert(typeof options.publicHost === 'string');
|
||||
this.address.setHost(options.publicHost);
|
||||
this.publicHost = IP.normalize(options.publicHost);
|
||||
}
|
||||
|
||||
if (options.publicPort != null) {
|
||||
assert(typeof options.port === 'number');
|
||||
this.address.setPort(options.publicPort);
|
||||
assert(typeof options.publicPort === 'number');
|
||||
assert(options.publicPort > 0 && options.publicPort <= 0xffff);
|
||||
this.publicPort = options.publicPort;
|
||||
}
|
||||
|
||||
if (options.maxOutbound != null) {
|
||||
assert(typeof options.maxOutbound === 'number');
|
||||
assert(options.maxOutbound > 0);
|
||||
this.maxOutbound = options.maxOutbound;
|
||||
}
|
||||
|
||||
@ -3558,6 +3723,11 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
this.onion = options.onion;
|
||||
}
|
||||
|
||||
if (options.upnp != null) {
|
||||
assert(typeof options.upnp === 'boolean');
|
||||
this.upnp = options.upnp;
|
||||
}
|
||||
|
||||
if (options.selfish) {
|
||||
assert(typeof options.selfish === 'boolean');
|
||||
this.selfish = options.selfish;
|
||||
@ -3669,9 +3839,6 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
this.requiredServices = options.requiredServices;
|
||||
}
|
||||
|
||||
this.address.services = this.services;
|
||||
this.address.ts = this.network.now();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -3791,7 +3958,10 @@ PoolOptions.prototype._createSocket = function createSocket(port, host) {
|
||||
*/
|
||||
|
||||
PoolOptions.prototype._resolve = function resolve(name) {
|
||||
return dns.resolve(name, this.proxy, this.onion);
|
||||
if (this.onion)
|
||||
return dns.lookup(name, this.proxy);
|
||||
|
||||
return dns.lookup(name);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -96,19 +96,6 @@ ProxySocket.prototype._init = function _init() {
|
||||
});
|
||||
};
|
||||
|
||||
ProxySocket.prototype.resolve = function resolve(name, record, callback) {
|
||||
var e;
|
||||
this.socket.emit('dns resolve', name, record, function(err, results) {
|
||||
if (err) {
|
||||
e = new Error(err.message);
|
||||
e.code = err.code || null;
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
callback(null, results);
|
||||
});
|
||||
};
|
||||
|
||||
ProxySocket.prototype.connect = function connect(port, host) {
|
||||
var nonce = 0;
|
||||
var i, pow;
|
||||
|
||||
41
lib/net/upnp-browser.js
Normal file
41
lib/net/upnp-browser.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* upnp-browser.js - upnp for bcoin
|
||||
* Copyright (c) 2017, Christopher Jeffrey (MIT License).
|
||||
* https://github.com/bcoin-org/bcoin
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* UPNP
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @param {String?} host - Multicast IP.
|
||||
* @param {Number?} port - Multicast port.
|
||||
* @param {String?} gateway - Gateway name.
|
||||
*/
|
||||
|
||||
function UPNP(host, port, gateway) {
|
||||
throw new Error('UPNP not supported.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover gateway and resolve service.
|
||||
* @param {String?} host - Multicast IP.
|
||||
* @param {Number?} port - Multicast port.
|
||||
* @param {String?} gateway - Gateway type.
|
||||
* @param {String[]?} targets - Target service types.
|
||||
* @returns {Promise} Service.
|
||||
*/
|
||||
|
||||
UPNP.discover = function discover(host, port, gateway, targets) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
reject(new Error('UPNP not supported.'));
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = UPNP;
|
||||
@ -60,7 +60,7 @@ UPNP.WAN_SERVICES = [
|
||||
* @default
|
||||
*/
|
||||
|
||||
UPNP.RESPONSE_TIMEOUT = 3000;
|
||||
UPNP.RESPONSE_TIMEOUT = 1000;
|
||||
|
||||
/**
|
||||
* Clean up current job.
|
||||
@ -430,14 +430,14 @@ UPNPService.prototype.addPortMapping = co(function* addPortMapping(remote, src,
|
||||
var local = IP.getPrivate();
|
||||
var xml, child;
|
||||
|
||||
if (!local)
|
||||
if (local.length === 0)
|
||||
throw new Error('Cannot determine local IP.');
|
||||
|
||||
xml = yield this.soapRequest(action, [
|
||||
['NewRemoteHost', remote],
|
||||
['NewExternalPort', src],
|
||||
['NewProtocol', 'TCP'],
|
||||
['NewInternalClient', local],
|
||||
['NewInternalClient', local[0]],
|
||||
['NewInternalPort', dest],
|
||||
['NewEnabled', 'True'],
|
||||
['NewPortMappingDescription', 'upnp:bcoin'],
|
||||
|
||||
@ -267,6 +267,7 @@ config.toOptions = function toOptions(data) {
|
||||
options.identityKey = key(data.identitykey);
|
||||
options.proxy = str(data.proxy);
|
||||
options.onion = bool(data.onion);
|
||||
options.upnp = bool(data.upnp);
|
||||
options.seeds = list(data.seeds);
|
||||
options.nodes = list(data.nodes);
|
||||
options.maxOutbound = num(data.maxoutbound);
|
||||
|
||||
@ -102,6 +102,7 @@ function FullNode(options) {
|
||||
maxInbound: this.options.maxInbound,
|
||||
proxy: this.options.proxy,
|
||||
onion: this.options.onion,
|
||||
upnp: this.options.upnp,
|
||||
seeds: this.options.seeds,
|
||||
nodes: this.options.nodes,
|
||||
publicHost: this.options.publicHost,
|
||||
|
||||
@ -62,6 +62,7 @@ function SPVNode(options) {
|
||||
chain: this.chain,
|
||||
proxy: this.options.proxy,
|
||||
onion: this.options.onion,
|
||||
upnp: this.options.upnp,
|
||||
seeds: this.options.seeds,
|
||||
nodes: this.options.nodes,
|
||||
bip151: this.options.bip151,
|
||||
|
||||
@ -168,6 +168,29 @@ NetAddress.prototype.isOnion = function isOnion() {
|
||||
return IP.isOnion(this.raw);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare against another network address.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
NetAddress.prototype.equal = function equal(addr) {
|
||||
return this.compare(addr) === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare against another network address.
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
NetAddress.prototype.compare = function compare(addr) {
|
||||
var cmp = util.cmp(this.raw, addr.raw);
|
||||
|
||||
if (cmp !== 0)
|
||||
return cmp;
|
||||
|
||||
return this.port - addr.port;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get reachable score to destination.
|
||||
* @param {NetAddress} dest
|
||||
@ -205,6 +228,7 @@ NetAddress.prototype.setHost = function setHost(host) {
|
||||
*/
|
||||
|
||||
NetAddress.prototype.setPort = function setPort(port) {
|
||||
assert(port >= 0 && port <= 0xffff);
|
||||
this.port = port;
|
||||
this.hostname = IP.toHostname(this.host, port);
|
||||
};
|
||||
|
||||
@ -43,17 +43,18 @@ main = network.main = {};
|
||||
main.type = 'main';
|
||||
|
||||
/**
|
||||
* Default seeds.
|
||||
* Default DNS seeds.
|
||||
* @const {String[]}
|
||||
* @default
|
||||
*/
|
||||
|
||||
main.seeds = [
|
||||
'seed.bitcoin.sipa.be', // Pieter Wuille
|
||||
'dnsseed.bluematt.me', // Matt Corallo
|
||||
'dnsseed.bitcoin.dashjr.org', // Luke Dashjr
|
||||
'seed.bitcoinstats.com', // Christian Decker
|
||||
'bitseed.xf2.org', // Jeff Garzik
|
||||
'seed.bitcoin.jonasschnelli.ch' // Jonas Schnelli
|
||||
'seed.bitcoin.jonasschnelli.ch', // Jonas Schnelli
|
||||
'seed.btc.petertodd.org' // Peter Todd
|
||||
];
|
||||
|
||||
/**
|
||||
@ -469,9 +470,10 @@ testnet = network.testnet = {};
|
||||
testnet.type = 'testnet';
|
||||
|
||||
testnet.seeds = [
|
||||
'testnet-seed.bitcoin.petertodd.org',
|
||||
'testnet-seed.bluematt.me',
|
||||
'testnet-seed.bitcoin.schildbach.de'
|
||||
'testnet-seed.bitcoin.jonasschnelli.ch', // Jonas Schnelli
|
||||
'seed.tbtc.petertodd.org', // Peter Todd
|
||||
'testnet-seed.bluematt.me', // Matt Corallo
|
||||
'testnet-seed.bitcoin.schildbach.de' // Andreas Schildbach
|
||||
];
|
||||
|
||||
testnet.magic = 0x0709110b;
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
* @module utils/co
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var nextTick = require('./nexttick');
|
||||
var every;
|
||||
|
||||
@ -230,6 +231,83 @@ every = co(function* every(jobs) {
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Start an interval. Wait for promise
|
||||
* to resolve on each iteration.
|
||||
* @param {Function} func
|
||||
* @param {Number?} time
|
||||
* @param {Object?} self
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
function startInterval(func, time, self) {
|
||||
var cb, ctx;
|
||||
|
||||
ctx = {
|
||||
timer: null,
|
||||
stopped: false
|
||||
};
|
||||
|
||||
cb = co(function* () {
|
||||
assert(ctx.timer != null);
|
||||
ctx.timer = null;
|
||||
|
||||
try {
|
||||
yield func.call(self);
|
||||
} finally {
|
||||
if (!ctx.stopped)
|
||||
ctx.timer = setTimeout(cb, time);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.timer = setTimeout(cb, time);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear an interval.
|
||||
* @param {Object} ctx
|
||||
*/
|
||||
|
||||
function stopInterval(ctx) {
|
||||
assert(ctx);
|
||||
if (ctx.timer != null) {
|
||||
clearTimeout(ctx.timer);
|
||||
ctx.timer = null;
|
||||
}
|
||||
ctx.stopped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a timeout.
|
||||
* @param {Function} func
|
||||
* @param {Number?} time
|
||||
* @param {Object?} self
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
function startTimeout(func, time, self) {
|
||||
return {
|
||||
timer: setTimeout(func.bind(self), time),
|
||||
stopped: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a timeout.
|
||||
* @param {Object} ctx
|
||||
*/
|
||||
|
||||
function stopTimeout(ctx) {
|
||||
assert(ctx);
|
||||
if (ctx.timer != null) {
|
||||
clearTimeout(ctx.timer);
|
||||
ctx.timer = null;
|
||||
}
|
||||
ctx.stopped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a job object.
|
||||
* @returns {Job}
|
||||
@ -282,6 +360,10 @@ exports.timeout = timeout;
|
||||
exports.wrap = wrap;
|
||||
exports.promisify = promisify;
|
||||
exports.every = every;
|
||||
exports.setInterval = startInterval;
|
||||
exports.clearInterval = stopInterval;
|
||||
exports.setTimeout = startTimeout;
|
||||
exports.clearTimeout = stopTimeout;
|
||||
exports.job = job;
|
||||
|
||||
module.exports = exports;
|
||||
|
||||
@ -984,15 +984,16 @@ IP.isEqual = function isEqual(a, b) {
|
||||
|
||||
/**
|
||||
* Get IP address from network interfaces.
|
||||
* @param {String} name - `public` or `private`.
|
||||
* @param {String} family - IP family name.
|
||||
* @param {String?} name - `public` or `private`.
|
||||
* @param {String?} family - IP family name.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
IP.getInterface = function getInterface(name, family) {
|
||||
IP.getInterfaces = function _getInterfaces(name, family) {
|
||||
var interfaces = getInterfaces();
|
||||
var keys = Object.keys(interfaces);
|
||||
var i, j, key, items, details, type, ip;
|
||||
var result = [];
|
||||
var i, j, key, items, details, type, raw;
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
key = keys[i];
|
||||
@ -1003,57 +1004,57 @@ IP.getInterface = function getInterface(name, family) {
|
||||
|
||||
type = details.family.toLowerCase();
|
||||
|
||||
if (type !== family)
|
||||
if (family && type !== family)
|
||||
continue;
|
||||
|
||||
if (details.internal)
|
||||
continue;
|
||||
|
||||
try {
|
||||
ip = IP.toBuffer(details.address);
|
||||
raw = IP.toBuffer(details.address);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IP.isLocal(ip))
|
||||
if (IP.isNull(raw))
|
||||
continue;
|
||||
|
||||
if (IP.isLocal(raw))
|
||||
continue;
|
||||
|
||||
if (name === 'public') {
|
||||
if (!IP.isRoutable(ip))
|
||||
if (!IP.isRoutable(raw))
|
||||
continue;
|
||||
} else if (name === 'private') {
|
||||
if (IP.isRoutable(raw))
|
||||
continue;
|
||||
}
|
||||
|
||||
return IP.toString(ip);
|
||||
result.push(IP.toString(raw));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get private IP from network interfaces.
|
||||
* @param {String?} family - IP family name.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
IP.getPrivate = function getPrivate() {
|
||||
var ip = IP.getInterface('private', 'ipv4');
|
||||
|
||||
if (ip)
|
||||
return ip;
|
||||
|
||||
return IP.getInterface('private', 'ipv6');
|
||||
IP.getPrivate = function getPrivate(family) {
|
||||
return IP.getInterfaces('private', family);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get public IP from network interfaces.
|
||||
* @param {String?} family - IP family name.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
IP.getPublic = function getPublic() {
|
||||
var ip = IP.getInterface('public', 'ipv4');
|
||||
|
||||
if (ip)
|
||||
return ip;
|
||||
|
||||
return IP.getInterface('public', 'ipv6');
|
||||
IP.getPublic = function getPublic(family) {
|
||||
return IP.getInterfaces('public', family);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -79,6 +79,7 @@
|
||||
"./lib/mempool/layout": "./lib/mempool/layout-browser.js",
|
||||
"./lib/net/dns": "./lib/net/dns-browser.js",
|
||||
"./lib/net/tcp": "./lib/net/tcp-browser.js",
|
||||
"./lib/net/upnp": "./lib/net/upnp-browser.js",
|
||||
"./lib/utils/native": "./browser/empty.js",
|
||||
"./lib/utils/nfkd": "./lib/utils/nfkd-browser.js",
|
||||
"./lib/utils/nexttick": "./lib/utils/nexttick-browser.js",
|
||||
@ -87,6 +88,7 @@
|
||||
"bcoin-native": "./browser/empty.js",
|
||||
"child_process": "./browser/empty.js",
|
||||
"crypto": "./browser/empty.js",
|
||||
"dgram": "./browser/empty.js",
|
||||
"fs": "./browser/empty.js",
|
||||
"net": "./browser/empty.js",
|
||||
"os": "./browser/empty.js",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user