refactor: options.
This commit is contained in:
parent
9dbfaf3127
commit
a486bd3a18
@ -34,13 +34,11 @@ var VerifyResult = errors.VerifyResult;
|
||||
* @param {String?} options.location - Database file location.
|
||||
* @param {String?} options.db - Database backend (`"leveldb"` by default).
|
||||
* @param {Number?} options.orphanLimit
|
||||
* @param {Number?} options.pendingLimit
|
||||
* @param {Boolean?} options.spv
|
||||
* @property {Boolean} loaded
|
||||
* @property {ChainDB} db - Note that Chain `options` will be passed
|
||||
* to the instantiated ChainDB.
|
||||
* @property {Number} total
|
||||
* @property {Number} orphanLimit
|
||||
* @property {Lock} locker
|
||||
* @property {Object} invalid
|
||||
* @property {ChainEntry?} tip
|
||||
@ -69,22 +67,20 @@ function Chain(options) {
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
this.options = new ChainOptions(options);
|
||||
|
||||
this.options = options;
|
||||
|
||||
this.network = Network.get(options.network);
|
||||
this.logger = options.logger || Logger.global;
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.db = new ChainDB(this);
|
||||
this.total = 0;
|
||||
this.orphanLimit = options.orphanLimit || (20 << 20);
|
||||
|
||||
this.locker = new Lock(true);
|
||||
this.invalid = new LRU(100);
|
||||
this.state = new DeploymentState();
|
||||
|
||||
this.tip = null;
|
||||
this.height = -1;
|
||||
this.synced = false;
|
||||
this.state = new DeploymentState();
|
||||
this.total = 0;
|
||||
this.startTime = util.hrtime();
|
||||
|
||||
this.orphanMap = {};
|
||||
@ -1492,7 +1488,7 @@ Chain.prototype.purgeOrphans = function purgeOrphans() {
|
||||
Chain.prototype.pruneOrphans = function pruneOrphans() {
|
||||
var i, hashes, hash, orphan, height, best, last;
|
||||
|
||||
if (this.orphanSize <= this.orphanLimit)
|
||||
if (this.orphanSize <= this.options.orphanLimit)
|
||||
return false;
|
||||
|
||||
hashes = Object.keys(this.orphanPrev);
|
||||
@ -2264,6 +2260,146 @@ Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, view, flags) {
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* ChainOptions
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function ChainOptions(options) {
|
||||
if (!(this instanceof ChainOptions))
|
||||
return new ChainOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = Logger.global;
|
||||
|
||||
this.location = null;
|
||||
this.db = 'memory';
|
||||
this.maxFiles = 64;
|
||||
this.cacheSize = 32 << 20;
|
||||
this.compression = true;
|
||||
this.bufferKeys = !util.isBrowser;
|
||||
|
||||
this.spv = false;
|
||||
this.witness = false;
|
||||
this.prune = false;
|
||||
this.indexTX = false;
|
||||
this.indexAddress = false;
|
||||
this.forceWitness = false;
|
||||
|
||||
this.coinCache = 0;
|
||||
this.entryCache = (2016 + 1) * 2 + 100;
|
||||
this.orphanLimit = 20 << 20;
|
||||
this.useCheckpoints = false;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {ChainOptions}
|
||||
*/
|
||||
|
||||
ChainOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
if (options.network != null)
|
||||
this.network = Network.get(options.network);
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.location != null) {
|
||||
assert(typeof options.location === 'string');
|
||||
this.location = options.location;
|
||||
}
|
||||
|
||||
if (options.db != null) {
|
||||
assert(typeof options.db === 'string');
|
||||
this.db = options.db;
|
||||
}
|
||||
|
||||
if (options.maxFiles != null) {
|
||||
assert(util.isNumber(options.maxFiles));
|
||||
this.maxFiles = options.maxFiles;
|
||||
}
|
||||
|
||||
if (options.cacheSize != null) {
|
||||
assert(util.isNumber(options.cacheSize));
|
||||
this.cacheSize = options.cacheSize;
|
||||
}
|
||||
|
||||
if (options.compression != null) {
|
||||
assert(typeof options.compression === 'boolean');
|
||||
this.compression = options.compression;
|
||||
}
|
||||
|
||||
if (options.spv != null) {
|
||||
assert(typeof options.spv === 'boolean');
|
||||
this.spv = options.spv;
|
||||
}
|
||||
|
||||
if (options.witness != null) {
|
||||
assert(typeof options.witness === 'boolean');
|
||||
this.witness = options.witness;
|
||||
}
|
||||
|
||||
if (options.prune != null) {
|
||||
assert(typeof options.prune === 'boolean');
|
||||
this.prune = options.prune;
|
||||
}
|
||||
|
||||
if (options.indexTX != null) {
|
||||
assert(typeof options.indexTX === 'boolean');
|
||||
this.indexTX = options.indexTX;
|
||||
}
|
||||
|
||||
if (options.indexAddress != null) {
|
||||
assert(typeof options.indexAddress === 'boolean');
|
||||
this.indexAddress = options.indexAddress;
|
||||
}
|
||||
|
||||
if (options.forceWitness != null) {
|
||||
assert(typeof options.forceWitness === 'boolean');
|
||||
this.forceWitness = options.forceWitness;
|
||||
}
|
||||
|
||||
if (options.coinCache != null) {
|
||||
assert(util.isNumber(options.coinCache));
|
||||
this.coinCache = options.coinCache;
|
||||
}
|
||||
|
||||
if (options.entryCache != null) {
|
||||
assert(util.isNumber(options.entryCache));
|
||||
this.entryCache = options.entryCache;
|
||||
}
|
||||
|
||||
if (options.orphanLimit != null) {
|
||||
assert(util.isNumber(options.orphanLimit));
|
||||
this.orphanLimit = options.orphanLimit;
|
||||
}
|
||||
|
||||
if (options.useCheckpoints != null) {
|
||||
assert(typeof options.useCheckpoints === 'boolean');
|
||||
this.useCheckpoints = options.useCheckpoints;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate chain options from object.
|
||||
* @param {Object} options
|
||||
* @returns {ChainOptions}
|
||||
*/
|
||||
|
||||
ChainOptions.fromOptions = function fromOptions(options) {
|
||||
return new ChainOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the deployment state of the chain.
|
||||
* @constructor
|
||||
|
||||
@ -35,7 +35,7 @@ var DUMMY = new Buffer([0]);
|
||||
* The database backend for the {@link Chain} object.
|
||||
* @exports ChainDB
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* @param {Chain} chain
|
||||
* @param {Boolean?} options.prune - Whether to prune the chain.
|
||||
* @param {Boolean?} options.spv - SPV-mode, will not save block
|
||||
* data, only entries.
|
||||
@ -54,41 +54,19 @@ function ChainDB(chain) {
|
||||
AsyncObject.call(this);
|
||||
|
||||
this.chain = chain;
|
||||
this.logger = chain.logger;
|
||||
this.network = chain.network;
|
||||
this.options = new ChainOptions(chain.options);
|
||||
this.options = chain.options;
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
|
||||
this.db = LDB({
|
||||
location: chain.options.location,
|
||||
db: chain.options.db,
|
||||
maxFiles: chain.options.maxFiles,
|
||||
cacheSize: chain.options.cacheSize || (32 << 20),
|
||||
compression: true,
|
||||
bufferKeys: !util.isBrowser
|
||||
});
|
||||
|
||||
this.stateCache = new StateCache(chain.network);
|
||||
this.db = LDB(this.options);
|
||||
this.stateCache = new StateCache(this.network);
|
||||
this.state = new ChainState();
|
||||
this.pending = null;
|
||||
this.current = null;
|
||||
|
||||
// We want at least 1 retarget interval cached
|
||||
// for retargetting, but we need at least two
|
||||
// cached for optimal versionbits state checks.
|
||||
// We add a padding of 100 for forked chains,
|
||||
// reorgs, chain locator creation and the bip34
|
||||
// check.
|
||||
this.cacheWindow = (this.network.pow.retargetInterval + 1) * 2 + 100;
|
||||
|
||||
// We want to keep the last 5 blocks of unspents in memory.
|
||||
this.coinWindow = chain.options.coinCache || 0;
|
||||
|
||||
this.coinCache = new LRU.Nil();
|
||||
this.cacheHash = new LRU(this.cacheWindow);
|
||||
this.cacheHeight = new LRU(this.cacheWindow);
|
||||
|
||||
if (this.coinWindow)
|
||||
this.coinCache = new LRU(this.coinWindow, getSize);
|
||||
this.coinCache = new LRU(this.options.coinCache, getSize);
|
||||
this.cacheHash = new LRU(this.options.entryCache);
|
||||
this.cacheHeight = new LRU(this.options.entryCache);
|
||||
}
|
||||
|
||||
util.inherits(ChainDB, AsyncObject);
|
||||
@ -118,7 +96,7 @@ ChainDB.prototype._open = co(function* open() {
|
||||
|
||||
if (state) {
|
||||
// Verify options have not changed.
|
||||
yield this.verifyOptions();
|
||||
yield this.verifyFlags();
|
||||
|
||||
// Verify deployment params have not changed.
|
||||
yield this.verifyDeployments();
|
||||
@ -133,7 +111,7 @@ ChainDB.prototype._open = co(function* open() {
|
||||
} else {
|
||||
// Database is fresh.
|
||||
// Write initial state.
|
||||
yield this.saveOptions();
|
||||
yield this.saveFlags();
|
||||
yield this.saveDeployments();
|
||||
yield this.saveGenesis();
|
||||
|
||||
@ -527,17 +505,17 @@ ChainDB.prototype.saveGenesis = co(function* saveGenesis() {
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieve the tip entry from the tip record.
|
||||
* @returns {Promise} - Returns {@link ChainOptions}.
|
||||
* Retrieve the database flags.
|
||||
* @returns {Promise} - Returns {@link ChainFlags}.
|
||||
*/
|
||||
|
||||
ChainDB.prototype.getOptions = co(function* getOptions() {
|
||||
ChainDB.prototype.getFlags = co(function* getFlags() {
|
||||
var data = yield this.db.get(layout.O);
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
return ChainOptions.fromRaw(data);
|
||||
return ChainFlags.fromRaw(data);
|
||||
});
|
||||
|
||||
/**
|
||||
@ -545,15 +523,15 @@ ChainDB.prototype.getOptions = co(function* getOptions() {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
ChainDB.prototype.verifyOptions = co(function* verifyOptions() {
|
||||
var options = yield this.getOptions();
|
||||
ChainDB.prototype.verifyFlags = co(function* verifyFlags() {
|
||||
var flags = yield this.getFlags();
|
||||
|
||||
assert(options, 'No options found.');
|
||||
assert(flags, 'No flags found.');
|
||||
|
||||
this.options.verify(options);
|
||||
flags.verify(this.options);
|
||||
|
||||
if (this.options.forceWitness)
|
||||
yield this.saveOptions();
|
||||
yield this.saveFlags();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1801,8 +1779,9 @@ ChainDB.prototype.pruneBlock = co(function* pruneBlock(entry) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
ChainDB.prototype.saveOptions = function saveOptions() {
|
||||
return this.db.put(layout.O, this.options.toRaw());
|
||||
ChainDB.prototype.saveFlags = function saveFlags() {
|
||||
var flags = ChainFlags.fromOptions(this.options);
|
||||
return this.db.put(layout.O, flags.toRaw());
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1914,9 +1893,9 @@ ChainDB.prototype.unindexTX = function unindexTX(tx, view) {
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function ChainOptions(options) {
|
||||
if (!(this instanceof ChainOptions))
|
||||
return new ChainOptions(options);
|
||||
function ChainFlags(options) {
|
||||
if (!(this instanceof ChainFlags))
|
||||
return new ChainFlags(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.spv = false;
|
||||
@ -1925,13 +1904,11 @@ function ChainOptions(options) {
|
||||
this.indexTX = false;
|
||||
this.indexAddress = false;
|
||||
|
||||
this.forceWitness = false;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
ChainOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
ChainFlags.prototype.fromOptions = function fromOptions(options) {
|
||||
this.network = Network.get(options.network);
|
||||
|
||||
if (options.spv != null) {
|
||||
@ -1959,56 +1936,51 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
this.indexAddress = options.indexAddress;
|
||||
}
|
||||
|
||||
if (options.forceWitness != null) {
|
||||
assert(typeof options.forceWitness === 'boolean');
|
||||
this.forceWitness = options.forceWitness;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ChainOptions.fromOptions = function fromOptions(data) {
|
||||
return new ChainOptions().fromOptions(data);
|
||||
ChainFlags.fromOptions = function fromOptions(data) {
|
||||
return new ChainFlags().fromOptions(data);
|
||||
};
|
||||
|
||||
ChainOptions.prototype.verify = function verify(options) {
|
||||
if (this.network !== options.network)
|
||||
ChainFlags.prototype.verify = function verify(options) {
|
||||
if (options.network !== this.network)
|
||||
throw new Error('Network mismatch for chain.');
|
||||
|
||||
if (this.spv && !options.spv)
|
||||
if (options.spv && !this.spv)
|
||||
throw new Error('Cannot retroactively enable SPV.');
|
||||
|
||||
if (!this.spv && options.spv)
|
||||
if (!options.spv && this.spv)
|
||||
throw new Error('Cannot retroactively disable SPV.');
|
||||
|
||||
if (!this.forceWitness) {
|
||||
if (this.witness && !options.witness)
|
||||
if (!options.forceWitness) {
|
||||
if (options.witness && !this.witness)
|
||||
throw new Error('Cannot retroactively enable witness.');
|
||||
|
||||
if (!this.witness && options.witness)
|
||||
if (!options.witness && this.witness)
|
||||
throw new Error('Cannot retroactively disable witness.');
|
||||
}
|
||||
|
||||
if (this.prune && !options.prune)
|
||||
if (options.prune && !this.prune)
|
||||
throw new Error('Cannot retroactively prune.');
|
||||
|
||||
if (!this.prune && options.prune)
|
||||
if (!options.prune && this.prune)
|
||||
throw new Error('Cannot retroactively unprune.');
|
||||
|
||||
if (this.indexTX && !options.indexTX)
|
||||
if (options.indexTX && !this.indexTX)
|
||||
throw new Error('Cannot retroactively enable TX indexing.');
|
||||
|
||||
if (!this.indexTX && options.indexTX)
|
||||
if (!options.indexTX && this.indexTX)
|
||||
throw new Error('Cannot retroactively disable TX indexing.');
|
||||
|
||||
if (this.indexAddress && !options.indexAddress)
|
||||
if (options.indexAddress && !this.indexAddress)
|
||||
throw new Error('Cannot retroactively enable address indexing.');
|
||||
|
||||
if (!this.indexAddress && options.indexAddress)
|
||||
if (!options.indexAddress && this.indexAddress)
|
||||
throw new Error('Cannot retroactively disable address indexing.');
|
||||
};
|
||||
|
||||
ChainOptions.prototype.toRaw = function toRaw() {
|
||||
ChainFlags.prototype.toRaw = function toRaw() {
|
||||
var bw = new StaticWriter(12);
|
||||
var flags = 0;
|
||||
|
||||
@ -2034,7 +2006,7 @@ ChainOptions.prototype.toRaw = function toRaw() {
|
||||
return bw.render();
|
||||
};
|
||||
|
||||
ChainOptions.prototype.fromRaw = function fromRaw(data) {
|
||||
ChainFlags.prototype.fromRaw = function fromRaw(data) {
|
||||
var br = new BufferReader(data);
|
||||
var flags;
|
||||
|
||||
@ -2051,8 +2023,8 @@ ChainOptions.prototype.fromRaw = function fromRaw(data) {
|
||||
return this;
|
||||
};
|
||||
|
||||
ChainOptions.fromRaw = function fromRaw(data) {
|
||||
return new ChainOptions().fromRaw(data);
|
||||
ChainFlags.fromRaw = function fromRaw(data) {
|
||||
return new ChainFlags().fromRaw(data);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
150
lib/http/base.js
150
lib/http/base.js
@ -25,24 +25,15 @@ function HTTPBase(options) {
|
||||
if (!(this instanceof HTTPBase))
|
||||
return new HTTPBase(options);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
this.options = options;
|
||||
this.io = null;
|
||||
this.options = new HTTPBaseOptions(options);
|
||||
|
||||
this.server = null;
|
||||
this.io = null;
|
||||
this.routes = new Routes();
|
||||
this.stack = [];
|
||||
|
||||
this.keyLimit = 100;
|
||||
this.bodyLimit = 20 << 20;
|
||||
|
||||
this.server = options.key
|
||||
? require('https').createServer(options)
|
||||
: require('http').createServer();
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
@ -55,6 +46,10 @@ util.inherits(HTTPBase, AsyncObject);
|
||||
|
||||
HTTPBase.prototype._init = function _init() {
|
||||
var self = this;
|
||||
var backend = this.options.getBackend();
|
||||
var options = this.options.toHTTP();
|
||||
|
||||
this.server = backend.createServer(options);
|
||||
|
||||
this._initRouter();
|
||||
this._initIO();
|
||||
@ -116,7 +111,7 @@ HTTPBase.prototype._initRouter = function _initRouter() {
|
||||
HTTPBase.prototype.handleRequest = co(function* handleRequest(req, res) {
|
||||
var i, routes, route, params;
|
||||
|
||||
initRequest(req, res, this.keyLimit);
|
||||
initRequest(req, res, this.options.keyLimit);
|
||||
|
||||
this.emit('request', req, res);
|
||||
|
||||
@ -170,7 +165,7 @@ HTTPBase.prototype.parseBody = co(function* parseBody(req) {
|
||||
body = JSON.parse(data);
|
||||
break;
|
||||
case 'form':
|
||||
body = parsePairs(data, this.keyLimit);
|
||||
body = parsePairs(data, this.options.keyLimit);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -254,7 +249,7 @@ HTTPBase.prototype._readBody = function _readBody(req, enc, resolve, reject) {
|
||||
total += data.length;
|
||||
hasData = true;
|
||||
|
||||
if (total > self.bodyLimit) {
|
||||
if (total > self.options.bodyLimit) {
|
||||
reject(new Error('Request body overflow.'));
|
||||
return;
|
||||
}
|
||||
@ -322,7 +317,6 @@ HTTPBase.prototype._initIO = function _initIO() {
|
||||
*/
|
||||
|
||||
HTTPBase.prototype._open = function open() {
|
||||
assert(typeof this.options.port === 'number', 'Port required.');
|
||||
return this.listen(this.options.port, this.options.host);
|
||||
};
|
||||
|
||||
@ -445,6 +439,130 @@ HTTPBase.prototype.listen = function listen(port, host) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* HTTP Base Options
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function HTTPBaseOptions(options) {
|
||||
if (!(this instanceof HTTPBaseOptions))
|
||||
return new HTTPBaseOptions(options);
|
||||
|
||||
this.host = '127.0.0.1';
|
||||
this.port = 8080;
|
||||
this.sockets = false;
|
||||
|
||||
this.ssl = false;
|
||||
this.key = null;
|
||||
this.cert = null;
|
||||
this.ca = null;
|
||||
|
||||
this.keyLimit = 100;
|
||||
this.bodyLimit = 20 << 20;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {HTTPBaseOptions}
|
||||
*/
|
||||
|
||||
HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
assert(options);
|
||||
|
||||
if (options.host != null) {
|
||||
assert(typeof options.host === 'string');
|
||||
this.host = options.host;
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
assert(typeof options.port === 'number', 'Port must be a number.');
|
||||
assert(options.port > 0 && options.port <= 0xffff);
|
||||
this.port = options.port;
|
||||
}
|
||||
|
||||
if (options.sockets != null) {
|
||||
assert(typeof options.sockets === 'boolean');
|
||||
this.sockets = options.sockets;
|
||||
}
|
||||
|
||||
if (options.key != null) {
|
||||
assert(typeof options.key === 'string' || Buffer.isBuffer(options.key));
|
||||
this.key = options.key;
|
||||
this.ssl = true;
|
||||
}
|
||||
|
||||
if (options.cert != null) {
|
||||
assert(typeof options.cert === 'string' || Buffer.isBuffer(options.cert));
|
||||
this.cert = options.cert;
|
||||
}
|
||||
|
||||
if (options.ca != null) {
|
||||
assert(Array.isArray(options.ca));
|
||||
this.ca = options.ca;
|
||||
}
|
||||
|
||||
if (options.keyLimit != null) {
|
||||
assert(typeof options.keyLimit === 'number');
|
||||
this.keyLimit = options.keyLimit;
|
||||
}
|
||||
|
||||
if (options.bodyLimit != null) {
|
||||
assert(typeof options.bodyLimit === 'number');
|
||||
this.bodyLimit = options.bodyLimit;
|
||||
}
|
||||
|
||||
if (options.ssl != null) {
|
||||
assert(typeof options.ssl === 'boolean');
|
||||
assert(this.key, 'SSL specified with no provided key.');
|
||||
this.ssl = options.ssl;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate http server options from object.
|
||||
* @param {Object} options
|
||||
* @returns {HTTPBaseOptions}
|
||||
*/
|
||||
|
||||
HTTPBaseOptions.fromOptions = function fromOptions(options) {
|
||||
return new HTTPBaseOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get HTTP server backend.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
HTTPBaseOptions.prototype.getBackend = function getBackend() {
|
||||
return this.ssl ? require('https') : require('http');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get HTTP server options.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
HTTPBaseOptions.prototype.toHTTP = function toHTTP() {
|
||||
if (!this.ssl)
|
||||
return undefined;
|
||||
|
||||
return {
|
||||
key: this.key,
|
||||
cert: this.cert,
|
||||
ca: this.ca
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Route
|
||||
* @constructor
|
||||
|
||||
@ -46,6 +46,8 @@ function RPC(node) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
assert(node, 'RPC requires a Node.');
|
||||
|
||||
this.node = node;
|
||||
this.network = node.network;
|
||||
this.chain = node.chain;
|
||||
@ -682,10 +684,10 @@ RPC.prototype.getblockchaininfo = co(function* getblockchaininfo(args) {
|
||||
mediantime: yield this.chain.tip.getMedianTimeAsync(),
|
||||
verificationprogress: this.chain.getProgress(),
|
||||
chainwork: this.chain.tip.chainwork.toString('hex', 64),
|
||||
pruned: this.chain.db.options.prune,
|
||||
pruned: this.chain.options.prune,
|
||||
softforks: this._getSoftforks(),
|
||||
bip9_softforks: yield this._getBIP9Softforks(),
|
||||
pruneheight: this.chain.db.options.prune
|
||||
pruneheight: this.chain.options.prune
|
||||
? Math.max(0, this.chain.height - this.chain.db.keepBlocks)
|
||||
: null
|
||||
};
|
||||
@ -754,10 +756,10 @@ RPC.prototype.getblock = co(function* getblock(args) {
|
||||
block = yield this.chain.db.getBlock(entry.hash);
|
||||
|
||||
if (!block) {
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
throw new RPCError('Block not available (spv mode)');
|
||||
|
||||
if (this.chain.db.prune)
|
||||
if (this.chain.options.prune)
|
||||
throw new RPCError('Block not available (pruned data)');
|
||||
|
||||
throw new RPCError('Can\'t read block from disk');
|
||||
@ -1000,8 +1002,8 @@ RPC.prototype.getmempoolinfo = co(function* getmempoolinfo(args) {
|
||||
size: this.mempool.totalTX,
|
||||
bytes: this.mempool.getSize(),
|
||||
usage: this.mempool.getSize(),
|
||||
maxmempool: this.mempool.maxSize,
|
||||
mempoolminfee: Amount.btc(this.mempool.minRelay, true)
|
||||
maxmempool: this.mempool.options.maxSize,
|
||||
mempoolminfee: Amount.btc(this.mempool.options.minRelay, true)
|
||||
};
|
||||
});
|
||||
|
||||
@ -1172,10 +1174,10 @@ RPC.prototype.gettxout = co(function* gettxout(args) {
|
||||
if (args.help || args.length < 2 || args.length > 3)
|
||||
throw new RPCError('gettxout "txid" n ( includemempool )');
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
throw new RPCError('Cannot get coins in SPV mode.');
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
throw new RPCError('Cannot get coins when pruned.');
|
||||
|
||||
hash = toHash(args[0]);
|
||||
@ -1214,10 +1216,10 @@ RPC.prototype.gettxoutproof = co(function* gettxoutproof(args) {
|
||||
if (args.help || (args.length !== 1 && args.length !== 2))
|
||||
throw new RPCError('gettxoutproof ["txid",...] ( blockhash )');
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
throw new RPCError('Cannot get coins in SPV mode.');
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
throw new RPCError('Cannot get coins when pruned.');
|
||||
|
||||
txids = toArray(args[0]);
|
||||
@ -1302,7 +1304,7 @@ RPC.prototype.gettxoutsetinfo = co(function* gettxoutsetinfo(args) {
|
||||
if (args.help || args.length !== 0)
|
||||
throw new RPCError('gettxoutsetinfo');
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
throw new RPCError('Chainstate not available (SPV mode).');
|
||||
|
||||
return {
|
||||
@ -1320,10 +1322,10 @@ RPC.prototype.verifychain = co(function* verifychain(args) {
|
||||
if (args.help || args.length > 2)
|
||||
throw new RPCError('verifychain ( checklevel numblocks )');
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
throw new RPCError('Cannot verify chain in SPV mode.');
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
throw new RPCError('Cannot verify chain when pruned.');
|
||||
|
||||
return null;
|
||||
@ -3199,7 +3201,7 @@ RPC.prototype.importprivkey = co(function* importprivkey(args) {
|
||||
if (args.length > 2)
|
||||
rescan = toBool(args[2]);
|
||||
|
||||
if (rescan && this.chain.db.options.prune)
|
||||
if (rescan && this.chain.options.prune)
|
||||
throw new RPCError('Cannot rescan when pruned.');
|
||||
|
||||
key = KeyRing.fromSecret(secret);
|
||||
@ -3229,7 +3231,7 @@ RPC.prototype.importwallet = co(function* importwallet(args) {
|
||||
if (args.length > 1)
|
||||
rescan = toBool(args[1]);
|
||||
|
||||
if (rescan && this.chain.db.options.prune)
|
||||
if (rescan && this.chain.options.prune)
|
||||
throw new RPCError('Cannot rescan when pruned.');
|
||||
|
||||
data = yield readFile(file, 'utf8');
|
||||
@ -3289,7 +3291,7 @@ RPC.prototype.importaddress = co(function* importaddress(args) {
|
||||
if (args.length > 3)
|
||||
p2sh = toBool(args[3]);
|
||||
|
||||
if (rescan && this.chain.db.options.prune)
|
||||
if (rescan && this.chain.options.prune)
|
||||
throw new RPCError('Cannot rescan when pruned.');
|
||||
|
||||
addr = Address.fromBase58(addr);
|
||||
@ -3320,7 +3322,7 @@ RPC.prototype.importpubkey = co(function* importpubkey(args) {
|
||||
if (args.length > 2)
|
||||
rescan = toBool(args[2]);
|
||||
|
||||
if (rescan && this.chain.db.options.prune)
|
||||
if (rescan && this.chain.options.prune)
|
||||
throw new RPCError('Cannot rescan when pruned.');
|
||||
|
||||
pubkey = new Buffer(pubkey, 'hex');
|
||||
|
||||
@ -25,6 +25,7 @@ var Outpoint = require('../primitives/outpoint');
|
||||
var HD = require('../hd/hd');
|
||||
var Script = require('../script/script');
|
||||
var crypto = require('../crypto/crypto');
|
||||
var Network = require('../protocol/network');
|
||||
var pkg = require('../../package.json');
|
||||
var cob = co.cob;
|
||||
var RPC;
|
||||
@ -43,17 +44,14 @@ function HTTPServer(options) {
|
||||
if (!(this instanceof HTTPServer))
|
||||
return new HTTPServer(options);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.options = options;
|
||||
this.node = options.node;
|
||||
this.options = new HTTPOptions(options);
|
||||
|
||||
assert(this.node, 'HTTP requires a Node.');
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.node = this.options.node;
|
||||
|
||||
this.network = this.node.network;
|
||||
this.chain = this.node.chain;
|
||||
this.mempool = this.node.mempool;
|
||||
this.pool = this.node.pool;
|
||||
@ -61,33 +59,10 @@ function HTTPServer(options) {
|
||||
this.miner = this.node.miner;
|
||||
this.wallet = this.node.wallet;
|
||||
this.walletdb = this.node.walletdb;
|
||||
this.logger = options.logger || this.node.logger;
|
||||
this.loaded = false;
|
||||
this.apiKey = options.apiKey;
|
||||
this.apiHash = null;
|
||||
this.serviceKey = options.serviceKey;
|
||||
this.serviceHash = null;
|
||||
|
||||
this.server = new HTTPBase(this.options);
|
||||
this.rpc = null;
|
||||
|
||||
if (!this.apiKey)
|
||||
this.apiKey = base58.encode(crypto.randomBytes(20));
|
||||
|
||||
if (!this.serviceKey)
|
||||
this.serviceKey = this.apiKey;
|
||||
|
||||
assert(typeof this.apiKey === 'string', 'API key must be a string.');
|
||||
assert(this.apiKey.length <= 200, 'API key must be under 200 bytes.');
|
||||
|
||||
assert(typeof this.serviceKey === 'string', 'API key must be a string.');
|
||||
assert(this.serviceKey.length <= 200, 'API key must be under 200 bytes.');
|
||||
|
||||
this.apiHash = hash256(this.apiKey);
|
||||
this.serviceHash = hash256(this.serviceKey);
|
||||
|
||||
options.sockets = true;
|
||||
|
||||
this.server = new HTTPBase(options);
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
@ -129,7 +104,7 @@ HTTPServer.prototype._init = function _init() {
|
||||
}
|
||||
|
||||
res.setHeader('X-Bcoin-Version', pkg.version);
|
||||
res.setHeader('X-Bcoin-Agent', this.pool.userAgent);
|
||||
res.setHeader('X-Bcoin-Agent', this.pool.options.agent);
|
||||
res.setHeader('X-Bcoin-Network', this.network.type);
|
||||
res.setHeader('X-Bcoin-Height', this.chain.height + '');
|
||||
res.setHeader('X-Bcoin-Tip', this.chain.tip.rhash());
|
||||
@ -167,7 +142,7 @@ HTTPServer.prototype._init = function _init() {
|
||||
hash = hash256(req.password);
|
||||
|
||||
// Regular API key gives access to everything.
|
||||
if (crypto.ccmp(hash, this.apiHash)) {
|
||||
if (crypto.ccmp(hash, this.options.apiHash)) {
|
||||
req.admin = true;
|
||||
return;
|
||||
}
|
||||
@ -175,7 +150,7 @@ HTTPServer.prototype._init = function _init() {
|
||||
// If they're hitting the wallet services,
|
||||
// they can use the less powerful API key.
|
||||
if (isWalletPath(req)) {
|
||||
if (crypto.ccmp(hash, this.serviceHash))
|
||||
if (crypto.ccmp(hash, this.options.serviceHash))
|
||||
return;
|
||||
}
|
||||
|
||||
@ -643,7 +618,7 @@ HTTPServer.prototype._init = function _init() {
|
||||
pool: {
|
||||
host: this.pool.address.host,
|
||||
port: this.pool.address.port,
|
||||
agent: this.pool.userAgent,
|
||||
agent: this.pool.options.agent,
|
||||
services: this.pool.address.services.toString(2),
|
||||
outbound: this.pool.peers.outbound,
|
||||
inbound: this.pool.peers.inbound
|
||||
@ -653,7 +628,7 @@ HTTPServer.prototype._init = function _init() {
|
||||
size: size
|
||||
},
|
||||
time: {
|
||||
uptime: Math.floor(util.uptime()),
|
||||
uptime: this.node.uptime(),
|
||||
system: util.now(),
|
||||
adjusted: this.network.now(),
|
||||
offset: this.network.time.offset
|
||||
@ -1329,8 +1304,8 @@ HTTPServer.prototype._initIO = function _initIO() {
|
||||
|
||||
if (!self.options.noAuth) {
|
||||
hash = hash256(key);
|
||||
api = crypto.ccmp(hash, self.apiHash);
|
||||
service = crypto.ccmp(hash, self.serviceHash);
|
||||
api = crypto.ccmp(hash, self.options.apiHash);
|
||||
service = crypto.ccmp(hash, self.options.serviceHash);
|
||||
|
||||
if (!api && !service)
|
||||
throw { error: 'Bad key.' };
|
||||
@ -1347,7 +1322,7 @@ HTTPServer.prototype._initIO = function _initIO() {
|
||||
|
||||
socket.emit('version', {
|
||||
version: pkg.version,
|
||||
agent: self.pool.userAgent,
|
||||
agent: self.pool.options.agent,
|
||||
network: self.network.type
|
||||
});
|
||||
});
|
||||
@ -1616,11 +1591,11 @@ HTTPServer.prototype.open = co(function* open() {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('HTTP API key: %s', this.apiKey);
|
||||
this.logger.info('HTTP Service API key: %s', this.serviceKey);
|
||||
this.logger.info('HTTP API key: %s', this.options.apiKey);
|
||||
this.logger.info('HTTP Service API key: %s', this.options.serviceKey);
|
||||
|
||||
this.apiKey = null;
|
||||
this.serviceKey = null;
|
||||
this.options.apiKey = null;
|
||||
this.options.serviceKey = null;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1697,6 +1672,127 @@ HTTPServer.prototype.listen = function listen(port, host) {
|
||||
return this.server.listen(port, host);
|
||||
};
|
||||
|
||||
/**
|
||||
* HTTPOptions
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function HTTPOptions(options) {
|
||||
if (!(this instanceof HTTPOptions))
|
||||
return new HTTPOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = null;
|
||||
this.node = null;
|
||||
this.apiKey = base58.encode(crypto.randomBytes(20));
|
||||
this.apiHash = hash256(this.apiKey);
|
||||
this.serviceKey = this.apiKey;
|
||||
this.serviceHash = this.apiHash;
|
||||
this.noAuth = false;
|
||||
this.walletAuth = false;
|
||||
this.sockets = true;
|
||||
this.host = '127.0.0.1';
|
||||
this.port = this.network.rpcPort;
|
||||
this.key = null;
|
||||
this.cert = null;
|
||||
this.ca = null;
|
||||
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {HTTPOptions}
|
||||
*/
|
||||
|
||||
HTTPOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
assert(options);
|
||||
assert(options.node && typeof options.node === 'object',
|
||||
'HTTP Server requires a Node.');
|
||||
|
||||
this.node = options.node;
|
||||
this.network = options.node.network;
|
||||
this.logger = options.node.logger;
|
||||
|
||||
this.port = this.network.rpcPort;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.apiKey != null) {
|
||||
assert(typeof options.apiKey === 'string',
|
||||
'API key must be a string.');
|
||||
assert(options.apiKey.length <= 200,
|
||||
'API key must be under 200 bytes.');
|
||||
this.apiKey = options.apiKey;
|
||||
this.apiHash = hash256(this.apiKey);
|
||||
this.serviceKey = this.apiKey;
|
||||
this.serviceHash = this.apiHash;
|
||||
}
|
||||
|
||||
if (options.serviceKey != null) {
|
||||
assert(typeof options.serviceKey === 'string',
|
||||
'API key must be a string.');
|
||||
assert(options.serviceKey.length <= 200,
|
||||
'API key must be under 200 bytes.');
|
||||
this.serviceKey = options.serviceKey;
|
||||
this.serviceHash = hash256(this.serviceKey);
|
||||
}
|
||||
|
||||
if (options.noAuth != null) {
|
||||
assert(typeof options.noAuth === 'boolean');
|
||||
this.noAuth = options.noAuth;
|
||||
}
|
||||
|
||||
if (options.walletAuth != null) {
|
||||
assert(typeof options.walletAuth === 'boolean');
|
||||
this.walletAuth = options.walletAuth;
|
||||
}
|
||||
|
||||
if (options.host != null) {
|
||||
assert(typeof options.host === 'string');
|
||||
this.host = options.host;
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
assert(typeof options.port === 'number', 'Port must be a number.');
|
||||
assert(options.port > 0 && options.port <= 0xffff);
|
||||
this.port = options.port;
|
||||
}
|
||||
|
||||
if (options.key != null) {
|
||||
assert(typeof options.key === 'string' || Buffer.isBuffer(options.key));
|
||||
this.key = options.key;
|
||||
}
|
||||
|
||||
if (options.cert != null) {
|
||||
assert(typeof options.cert === 'string' || Buffer.isBuffer(options.cert));
|
||||
this.cert = options.cert;
|
||||
}
|
||||
|
||||
if (options.ca != null) {
|
||||
assert(Array.isArray(options.ca));
|
||||
this.ca = options.ca;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate http options from object.
|
||||
* @param {Object} options
|
||||
* @returns {HTTPOptions}
|
||||
*/
|
||||
|
||||
HTTPOptions.fromOptions = function fromOptions(options) {
|
||||
return new HTTPOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* ClientSocket
|
||||
* @constructor
|
||||
|
||||
@ -26,6 +26,7 @@ var TXMeta = require('../primitives/txmeta');
|
||||
var MempoolEntry = require('./mempoolentry');
|
||||
var CoinView = require('../coins/coinview');
|
||||
var Coins = require('../coins/coins');
|
||||
var Network = require('../protocol/network');
|
||||
var VerifyError = errors.VerifyError;
|
||||
var VerifyResult = errors.VerifyResult;
|
||||
|
||||
@ -66,128 +67,33 @@ function Mempool(options) {
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
this.options = new MempoolOptions(options);
|
||||
|
||||
this.options = options;
|
||||
this.chain = options.chain;
|
||||
this.fees = options.fees;
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.chain = this.options.chain;
|
||||
this.fees = this.options.fees;
|
||||
|
||||
assert(this.chain, 'Mempool requires a blockchain.');
|
||||
|
||||
this.network = this.chain.network;
|
||||
this.logger = options.logger || this.chain.logger;
|
||||
this.locker = new Lock(true);
|
||||
|
||||
this.size = 0;
|
||||
this.totalOrphans = 0;
|
||||
this.totalTX = 0;
|
||||
this.freeCount = 0;
|
||||
this.lastTime = 0;
|
||||
|
||||
this.waiting = {};
|
||||
this.orphans = {};
|
||||
this.map = {};
|
||||
this.spents = {};
|
||||
this.coinIndex = new CoinIndex(this);
|
||||
this.txIndex = new TXIndex(this);
|
||||
|
||||
this.rejects = new Bloom.Rolling(120000, 0.000001);
|
||||
|
||||
this.freeCount = 0;
|
||||
this.lastTime = 0;
|
||||
|
||||
this.limitFree = true;
|
||||
this.limitFreeRelay = 15;
|
||||
this.relayPriority = true;
|
||||
this.requireStandard = this.network.requireStandard;
|
||||
this.rejectAbsurdFees = true;
|
||||
this.prematureWitness = false;
|
||||
this.paranoidChecks = false;
|
||||
this.replaceByFee = false;
|
||||
|
||||
this.maxSize = policy.MEMPOOL_MAX_SIZE;
|
||||
this.maxOrphans = policy.MEMPOOL_MAX_ORPHANS;
|
||||
this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS;
|
||||
this.expiryTime = policy.MEMPOOL_EXPIRY_TIME;
|
||||
this.minRelay = this.network.minRelay;
|
||||
|
||||
this._initOptions(options);
|
||||
this.coinIndex = new CoinIndex(this);
|
||||
this.txIndex = new TXIndex(this);
|
||||
}
|
||||
|
||||
util.inherits(Mempool, AsyncObject);
|
||||
|
||||
/**
|
||||
* Initialize options.
|
||||
* @param {Object} options
|
||||
* @private
|
||||
*/
|
||||
|
||||
Mempool.prototype._initOptions = function _initOptions(options) {
|
||||
if (options.limitFree != null) {
|
||||
assert(typeof options.limitFree === 'boolean');
|
||||
this.limitFree = options.limitFree;
|
||||
}
|
||||
|
||||
if (options.limitFreeRelay != null) {
|
||||
assert(util.isNumber(options.limitFreeRelay));
|
||||
this.limitFreeRelay = options.limitFreeRelay;
|
||||
}
|
||||
|
||||
if (options.relayPriority != null) {
|
||||
assert(typeof options.relayPriority === 'boolean');
|
||||
this.relayPriority = options.relayPriority;
|
||||
}
|
||||
|
||||
if (options.requireStandard != null) {
|
||||
assert(typeof options.requireStandard === 'boolean');
|
||||
this.requireStandard = options.requireStandard;
|
||||
}
|
||||
|
||||
if (options.rejectAbsurdFees != null) {
|
||||
assert(typeof options.rejectAbsurdFees === 'boolean');
|
||||
this.rejectAbsurdFees = options.rejectAbsurdFees;
|
||||
}
|
||||
|
||||
if (options.prematureWitness != null) {
|
||||
assert(typeof options.prematureWitness === 'boolean');
|
||||
this.prematureWitness = options.prematureWitness;
|
||||
}
|
||||
|
||||
if (options.paranoidChecks != null) {
|
||||
assert(typeof options.paranoidChecks === 'boolean');
|
||||
this.paranoidChecks = options.paranoidChecks;
|
||||
}
|
||||
|
||||
if (options.replaceByFee != null) {
|
||||
assert(typeof options.replaceByFee === 'boolean');
|
||||
this.replaceByFee = options.replaceByFee;
|
||||
}
|
||||
|
||||
if (options.maxSize != null) {
|
||||
assert(util.isNumber(options.maxSize));
|
||||
this.maxSize = options.maxSize;
|
||||
}
|
||||
|
||||
if (options.maxOrphans != null) {
|
||||
assert(util.isNumber(options.maxOrphans));
|
||||
this.maxOrphans = options.maxOrphans;
|
||||
}
|
||||
|
||||
if (options.maxAncestors != null) {
|
||||
assert(util.isNumber(options.maxAncestors));
|
||||
this.maxAncestors = options.maxAncestors;
|
||||
}
|
||||
|
||||
if (options.expiryTime != null) {
|
||||
assert(util.isNumber(options.expiryTime));
|
||||
this.expiryTime = options.expiryTime;
|
||||
}
|
||||
|
||||
if (options.minRelay != null) {
|
||||
assert(util.isNumber(options.minRelay));
|
||||
this.minRelay = options.minRelay;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the chain, wait for the database to load.
|
||||
* @alias Mempool#open
|
||||
@ -195,7 +101,7 @@ Mempool.prototype._initOptions = function _initOptions(options) {
|
||||
*/
|
||||
|
||||
Mempool.prototype._open = co(function* open() {
|
||||
var size = (this.maxSize / 1024).toFixed(2);
|
||||
var size = (this.options.maxSize / 1024).toFixed(2);
|
||||
yield this.chain.open();
|
||||
this.logger.info('Mempool loaded (maxsize=%dkb).', size);
|
||||
});
|
||||
@ -385,11 +291,11 @@ Mempool.prototype.limitSize = function limitSize(entryHash) {
|
||||
var trimmed = false;
|
||||
var i, hashes, hash, end, entry;
|
||||
|
||||
if (this.getSize() <= this.maxSize)
|
||||
if (this.getSize() <= this.options.maxSize)
|
||||
return trimmed;
|
||||
|
||||
hashes = this.getSnapshot();
|
||||
end = util.now() - this.expiryTime;
|
||||
end = util.now() - this.options.expiryTime;
|
||||
|
||||
for (i = 0; i < hashes.length; i++) {
|
||||
hash = hashes[i];
|
||||
@ -406,7 +312,7 @@ Mempool.prototype.limitSize = function limitSize(entryHash) {
|
||||
|
||||
this.removeEntry(entry, true);
|
||||
|
||||
if (this.getSize() <= this.maxSize)
|
||||
if (this.getSize() <= this.options.maxSize)
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
@ -424,7 +330,7 @@ Mempool.prototype.limitSize = function limitSize(entryHash) {
|
||||
|
||||
this.removeEntry(entry, true);
|
||||
|
||||
if (this.getSize() <= this.maxSize)
|
||||
if (this.getSize() <= this.options.maxSize)
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
@ -439,7 +345,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() {
|
||||
var orphans = Object.keys(this.orphans);
|
||||
var i, hash;
|
||||
|
||||
while (this.totalOrphans > this.maxOrphans) {
|
||||
while (this.totalOrphans > this.options.maxOrphans) {
|
||||
i = crypto.randomRange(0, orphans.length);
|
||||
hash = orphans[i];
|
||||
orphans.splice(i, 1);
|
||||
@ -757,7 +663,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
}
|
||||
|
||||
// Do not allow CSV until it's activated.
|
||||
if (this.requireStandard) {
|
||||
if (this.options.requireStandard) {
|
||||
if (!this.chain.state.hasCSV() && tx.version >= 2) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
@ -767,7 +673,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
}
|
||||
|
||||
// Do not allow segwit until it's activated.
|
||||
if (!this.chain.state.hasWitness() && !this.prematureWitness) {
|
||||
if (!this.chain.state.hasWitness() && !this.options.prematureWitness) {
|
||||
if (tx.hasWitness()) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
@ -777,14 +683,14 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
|
||||
}
|
||||
|
||||
// Non-contextual standardness checks.
|
||||
if (this.requireStandard) {
|
||||
if (this.options.requireStandard) {
|
||||
if (!tx.isStandard(ret)) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
ret.reason,
|
||||
ret.score);
|
||||
}
|
||||
if (!this.replaceByFee) {
|
||||
if (!this.options.replaceByFee) {
|
||||
if (tx.isRBF()) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
@ -884,7 +790,7 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
||||
}
|
||||
|
||||
// Check input an witness standardness.
|
||||
if (this.requireStandard) {
|
||||
if (this.options.requireStandard) {
|
||||
if (!tx.hasStandardInputs(view)) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
@ -912,9 +818,9 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
||||
}
|
||||
|
||||
// Make sure this guy gave a decent fee.
|
||||
minFee = tx.getMinFee(entry.size, this.minRelay);
|
||||
minFee = tx.getMinFee(entry.size, this.options.minRelay);
|
||||
|
||||
if (this.relayPriority && entry.fee < minFee) {
|
||||
if (this.options.relayPriority && entry.fee < minFee) {
|
||||
if (!entry.isFree(height)) {
|
||||
throw new VerifyError(tx,
|
||||
'insufficientfee',
|
||||
@ -925,7 +831,7 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
||||
|
||||
// Continuously rate-limit free (really, very-low-fee)
|
||||
// transactions. This mitigates 'penny-flooding'.
|
||||
if (this.limitFree && entry.fee < minFee) {
|
||||
if (this.options.limitFree && entry.fee < minFee) {
|
||||
now = util.now();
|
||||
|
||||
// Use an exponentially decaying ~10-minute window.
|
||||
@ -934,7 +840,7 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
||||
|
||||
// The limitFreeRelay unit is thousand-bytes-per-minute
|
||||
// At default rate it would take over a month to fill 1GB.
|
||||
if (this.freeCount > this.limitFreeRelay * 10 * 1000) {
|
||||
if (this.freeCount > this.options.limitFreeRelay * 10 * 1000) {
|
||||
throw new VerifyError(tx,
|
||||
'insufficientfee',
|
||||
'rate limited free transaction',
|
||||
@ -945,11 +851,11 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
||||
}
|
||||
|
||||
// Important safety feature.
|
||||
if (this.rejectAbsurdFees && entry.fee > minFee * 10000)
|
||||
if (this.options.rejectAbsurdFees && entry.fee > minFee * 10000)
|
||||
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
|
||||
|
||||
// Why do we have this here? Nested transactions are cool.
|
||||
if (this.countAncestors(tx) > this.maxAncestors) {
|
||||
if (this.countAncestors(tx) > this.options.maxAncestors) {
|
||||
throw new VerifyError(tx,
|
||||
'nonstandard',
|
||||
'too-long-mempool-chain',
|
||||
@ -992,7 +898,7 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
||||
}
|
||||
|
||||
// Paranoid checks.
|
||||
if (this.paranoidChecks) {
|
||||
if (this.options.paranoidChecks) {
|
||||
flags = Script.flags.MANDATORY_VERIFY_FLAGS;
|
||||
result = yield this.verifyResult(tx, view, flags);
|
||||
assert(result, 'BUG: Verify failed for mandatory but not standard.');
|
||||
@ -1143,12 +1049,12 @@ Mempool.prototype._countAncestors = function countAncestors(tx, count, set) {
|
||||
set[hash] = true;
|
||||
count += 1;
|
||||
|
||||
if (count > this.maxAncestors)
|
||||
if (count > this.options.maxAncestors)
|
||||
break;
|
||||
|
||||
count = this._countAncestors(prev, count, set);
|
||||
|
||||
if (count > this.maxAncestors)
|
||||
if (count > this.options.maxAncestors)
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1941,6 +1847,146 @@ Mempool.prototype.getSize = function getSize() {
|
||||
return this.size;
|
||||
};
|
||||
|
||||
/**
|
||||
* MempoolOptions
|
||||
* @constructor
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
function MempoolOptions(options) {
|
||||
if (!(this instanceof MempoolOptions))
|
||||
return new MempoolOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.chain = null;
|
||||
this.logger = null;
|
||||
this.fees = null;
|
||||
|
||||
this.limitFree = true;
|
||||
this.limitFreeRelay = 15;
|
||||
this.relayPriority = true;
|
||||
this.requireStandard = this.network.requireStandard;
|
||||
this.rejectAbsurdFees = true;
|
||||
this.prematureWitness = false;
|
||||
this.paranoidChecks = false;
|
||||
this.replaceByFee = false;
|
||||
|
||||
this.maxSize = policy.MEMPOOL_MAX_SIZE;
|
||||
this.maxOrphans = policy.MEMPOOL_MAX_ORPHANS;
|
||||
this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS;
|
||||
this.expiryTime = policy.MEMPOOL_EXPIRY_TIME;
|
||||
this.minRelay = this.network.minRelay;
|
||||
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {MempoolOptions}
|
||||
*/
|
||||
|
||||
MempoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
assert(options, 'Mempool requires options.');
|
||||
assert(options.chain && typeof options.chain === 'object',
|
||||
'Mempool requires a blockchain.');
|
||||
|
||||
this.chain = options.chain;
|
||||
this.network = options.chain.network;
|
||||
this.logger = options.chain.logger;
|
||||
|
||||
this.requireStandard = this.network.requireStandard;
|
||||
this.minRelay = this.network.minRelay;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.fees != null) {
|
||||
assert(typeof options.fees === 'object');
|
||||
this.fees = options.fees;
|
||||
}
|
||||
|
||||
if (options.limitFree != null) {
|
||||
assert(typeof options.limitFree === 'boolean');
|
||||
this.limitFree = options.limitFree;
|
||||
}
|
||||
|
||||
if (options.limitFreeRelay != null) {
|
||||
assert(util.isNumber(options.limitFreeRelay));
|
||||
this.limitFreeRelay = options.limitFreeRelay;
|
||||
}
|
||||
|
||||
if (options.relayPriority != null) {
|
||||
assert(typeof options.relayPriority === 'boolean');
|
||||
this.relayPriority = options.relayPriority;
|
||||
}
|
||||
|
||||
if (options.requireStandard != null) {
|
||||
assert(typeof options.requireStandard === 'boolean');
|
||||
this.requireStandard = options.requireStandard;
|
||||
}
|
||||
|
||||
if (options.rejectAbsurdFees != null) {
|
||||
assert(typeof options.rejectAbsurdFees === 'boolean');
|
||||
this.rejectAbsurdFees = options.rejectAbsurdFees;
|
||||
}
|
||||
|
||||
if (options.prematureWitness != null) {
|
||||
assert(typeof options.prematureWitness === 'boolean');
|
||||
this.prematureWitness = options.prematureWitness;
|
||||
}
|
||||
|
||||
if (options.paranoidChecks != null) {
|
||||
assert(typeof options.paranoidChecks === 'boolean');
|
||||
this.paranoidChecks = options.paranoidChecks;
|
||||
}
|
||||
|
||||
if (options.replaceByFee != null) {
|
||||
assert(typeof options.replaceByFee === 'boolean');
|
||||
this.replaceByFee = options.replaceByFee;
|
||||
}
|
||||
|
||||
if (options.maxSize != null) {
|
||||
assert(util.isNumber(options.maxSize));
|
||||
this.maxSize = options.maxSize;
|
||||
}
|
||||
|
||||
if (options.maxOrphans != null) {
|
||||
assert(util.isNumber(options.maxOrphans));
|
||||
this.maxOrphans = options.maxOrphans;
|
||||
}
|
||||
|
||||
if (options.maxAncestors != null) {
|
||||
assert(util.isNumber(options.maxAncestors));
|
||||
this.maxAncestors = options.maxAncestors;
|
||||
}
|
||||
|
||||
if (options.expiryTime != null) {
|
||||
assert(util.isNumber(options.expiryTime));
|
||||
this.expiryTime = options.expiryTime;
|
||||
}
|
||||
|
||||
if (options.minRelay != null) {
|
||||
assert(util.isNumber(options.minRelay));
|
||||
this.minRelay = options.minRelay;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate mempool options from object.
|
||||
* @param {Object} options
|
||||
* @returns {MempoolOptions}
|
||||
*/
|
||||
|
||||
MempoolOptions.fromOptions = function fromOptions(options) {
|
||||
return new MempoolOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* TX Address Index
|
||||
*/
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* https://github.com/bcoin-org/bcoin
|
||||
*/
|
||||
|
||||
var crypto = require('../crypto/crypto');
|
||||
var assert = require('assert');
|
||||
var crypto = require('../crypto/crypto');
|
||||
|
||||
/**
|
||||
* Hash until the nonce overflows.
|
||||
|
||||
@ -13,9 +13,10 @@ var co = require('../utils/co');
|
||||
var AsyncObject = require('../utils/async');
|
||||
var Address = require('../primitives/address');
|
||||
var MinerBlock = require('./minerblock');
|
||||
var BlockEntry = MinerBlock.BlockEntry;
|
||||
var Network = require('../protocol/network');
|
||||
var consensus = require('../protocol/consensus');
|
||||
var policy = require('../protocol/policy');
|
||||
var BlockEntry = MinerBlock.BlockEntry;
|
||||
|
||||
/**
|
||||
* A bitcoin miner (supports mining witness blocks).
|
||||
@ -36,92 +37,24 @@ function Miner(options) {
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
assert(options, 'Miner requires options.');
|
||||
assert(options.chain, 'Miner requires a blockchain.');
|
||||
this.options = new MinerOptions(options);
|
||||
|
||||
this.chain = options.chain;
|
||||
this.mempool = options.mempool;
|
||||
this.network = this.chain.network;
|
||||
this.logger = options.logger || this.chain.logger;
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.chain = this.options.chain;
|
||||
this.mempool = this.options.mempool;
|
||||
this.addresses = this.options.addresses;
|
||||
|
||||
this.running = false;
|
||||
this.stopping = false;
|
||||
this.attempt = null;
|
||||
this.since = 0;
|
||||
|
||||
this.version = -1;
|
||||
this.addresses = [];
|
||||
this.coinbaseFlags = new Buffer('mined by bcoin', 'ascii');
|
||||
|
||||
this.minWeight = policy.MIN_BLOCK_WEIGHT;
|
||||
this.maxWeight = policy.MAX_BLOCK_WEIGHT;
|
||||
this.priorityWeight = policy.PRIORITY_BLOCK_WEIGHT;
|
||||
this.minPriority = policy.MIN_BLOCK_PRIORITY;
|
||||
this.maxSigops = consensus.MAX_BLOCK_SIGOPS_COST;
|
||||
|
||||
this._initOptions(options);
|
||||
this._init();
|
||||
}
|
||||
|
||||
util.inherits(Miner, AsyncObject);
|
||||
|
||||
/**
|
||||
* Initialize the miner options.
|
||||
* @private
|
||||
*/
|
||||
|
||||
Miner.prototype._initOptions = function _initOptions(options) {
|
||||
var i, flags;
|
||||
|
||||
if (options.version != null) {
|
||||
assert(util.isNumber(options.version));
|
||||
this.version = options.version;
|
||||
}
|
||||
|
||||
if (options.address)
|
||||
this.addAddress(options.address);
|
||||
|
||||
if (options.addresses) {
|
||||
assert(Array.isArray(options.addresses));
|
||||
for (i = 0; i < options.addresses.length; i++)
|
||||
this.addAddress(options.addresses[i]);
|
||||
}
|
||||
|
||||
if (options.coinbaseFlags) {
|
||||
flags = options.coinbaseFlags;
|
||||
if (typeof flags === 'string')
|
||||
flags = new Buffer(flags, 'utf8');
|
||||
assert(Buffer.isBuffer(flags));
|
||||
this.coinbaseFlags = flags;
|
||||
}
|
||||
|
||||
if (options.minWeight != null) {
|
||||
assert(util.isNumber(options.minWeight));
|
||||
this.minWeight = options.minWeight;
|
||||
}
|
||||
|
||||
if (options.maxWeight != null) {
|
||||
assert(util.isNumber(options.maxWeight));
|
||||
this.maxWeight = options.maxWeight;
|
||||
}
|
||||
|
||||
if (options.maxSigops != null) {
|
||||
assert(util.isNumber(options.maxSigops));
|
||||
assert(options.maxSigops <= consensus.MAX_BLOCK_SIGOPS_COST);
|
||||
this.maxSigops = options.maxSigops;
|
||||
}
|
||||
|
||||
if (options.priorityWeight != null) {
|
||||
assert(util.isNumber(options.priorityWeight));
|
||||
this.priorityWeight = options.priorityWeight;
|
||||
}
|
||||
|
||||
if (options.minPriority != null) {
|
||||
assert(util.isNumber(options.minPriority));
|
||||
this.minPriority = options.minPriority;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the miner.
|
||||
* @private
|
||||
@ -168,7 +101,7 @@ Miner.prototype._open = co(function* open() {
|
||||
yield this.mempool.open();
|
||||
|
||||
this.logger.info('Miner loaded (flags=%s).',
|
||||
this.coinbaseFlags.toString('utf8'));
|
||||
this.options.coinbaseFlags.toString('utf8'));
|
||||
});
|
||||
|
||||
/**
|
||||
@ -306,7 +239,7 @@ Miner.prototype._onStop = function _onStop() {
|
||||
*/
|
||||
|
||||
Miner.prototype.createBlock = co(function* createBlock(tip, address) {
|
||||
var version = this.version;
|
||||
var version = this.options.version;
|
||||
var ts, locktime, target, attempt;
|
||||
|
||||
if (!tip)
|
||||
@ -335,7 +268,7 @@ Miner.prototype.createBlock = co(function* createBlock(tip, address) {
|
||||
locktime: locktime,
|
||||
flags: this.chain.state.flags,
|
||||
address: address,
|
||||
coinbaseFlags: this.coinbaseFlags,
|
||||
coinbaseFlags: this.options.coinbaseFlags,
|
||||
witness: this.chain.state.hasWitness(),
|
||||
network: this.network
|
||||
});
|
||||
@ -457,16 +390,17 @@ Miner.prototype.build = function build(attempt) {
|
||||
|
||||
weight += tx.getWeight();
|
||||
|
||||
if (weight > this.maxWeight)
|
||||
if (weight > this.options.maxWeight)
|
||||
continue;
|
||||
|
||||
sigops += item.sigops;
|
||||
|
||||
if (sigops > this.maxSigops)
|
||||
if (sigops > this.options.maxSigops)
|
||||
continue;
|
||||
|
||||
if (priority) {
|
||||
if (weight > this.priorityWeight || item.priority < this.minPriority) {
|
||||
if (weight > this.options.priorityWeight
|
||||
|| item.priority < this.options.minPriority) {
|
||||
// Todo: Compare descendant rate with
|
||||
// cumulative fees and cumulative vsize.
|
||||
queue.cmp = cmpRate;
|
||||
@ -475,7 +409,7 @@ Miner.prototype.build = function build(attempt) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (item.free && weight >= this.minWeight)
|
||||
if (item.free && weight >= this.options.minWeight)
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -504,6 +438,123 @@ Miner.prototype.build = function build(attempt) {
|
||||
assert(block.getWeight() <= attempt.weight);
|
||||
};
|
||||
|
||||
/**
|
||||
* MinerOptions
|
||||
* @constructor
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
function MinerOptions(options) {
|
||||
if (!(this instanceof MinerOptions))
|
||||
return new MinerOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = null;
|
||||
this.chain = null;
|
||||
this.mempool = null;
|
||||
|
||||
this.version = -1;
|
||||
this.addresses = [];
|
||||
this.coinbaseFlags = new Buffer('mined by bcoin', 'ascii');
|
||||
|
||||
this.minWeight = policy.MIN_BLOCK_WEIGHT;
|
||||
this.maxWeight = policy.MAX_BLOCK_WEIGHT;
|
||||
this.priorityWeight = policy.PRIORITY_BLOCK_WEIGHT;
|
||||
this.minPriority = policy.MIN_BLOCK_PRIORITY;
|
||||
this.maxSigops = consensus.MAX_BLOCK_SIGOPS_COST;
|
||||
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {MinerOptions}
|
||||
*/
|
||||
|
||||
MinerOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
var i, flags;
|
||||
|
||||
assert(options, 'Miner requires options.');
|
||||
assert(options.chain && typeof options.chain === 'object',
|
||||
'Miner requires a blockchain.');
|
||||
|
||||
this.chain = options.chain;
|
||||
this.network = options.chain.network;
|
||||
this.logger = options.chain.logger;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.mempool != null) {
|
||||
assert(typeof options.mempool === 'object');
|
||||
this.mempool = options.mempool;
|
||||
}
|
||||
|
||||
if (options.version != null) {
|
||||
assert(util.isNumber(options.version));
|
||||
this.version = options.version;
|
||||
}
|
||||
|
||||
if (options.address)
|
||||
this.addresses.push(new Address(options.address));
|
||||
|
||||
if (options.addresses) {
|
||||
assert(Array.isArray(options.addresses));
|
||||
for (i = 0; i < options.addresses.length; i++)
|
||||
this.addresses.push(new Address(options.addresses[i]));
|
||||
}
|
||||
|
||||
if (options.coinbaseFlags) {
|
||||
flags = options.coinbaseFlags;
|
||||
if (typeof flags === 'string')
|
||||
flags = new Buffer(flags, 'utf8');
|
||||
assert(Buffer.isBuffer(flags));
|
||||
this.coinbaseFlags = flags;
|
||||
}
|
||||
|
||||
if (options.minWeight != null) {
|
||||
assert(util.isNumber(options.minWeight));
|
||||
this.minWeight = options.minWeight;
|
||||
}
|
||||
|
||||
if (options.maxWeight != null) {
|
||||
assert(util.isNumber(options.maxWeight));
|
||||
this.maxWeight = options.maxWeight;
|
||||
}
|
||||
|
||||
if (options.maxSigops != null) {
|
||||
assert(util.isNumber(options.maxSigops));
|
||||
assert(options.maxSigops <= consensus.MAX_BLOCK_SIGOPS_COST);
|
||||
this.maxSigops = options.maxSigops;
|
||||
}
|
||||
|
||||
if (options.priorityWeight != null) {
|
||||
assert(util.isNumber(options.priorityWeight));
|
||||
this.priorityWeight = options.priorityWeight;
|
||||
}
|
||||
|
||||
if (options.minPriority != null) {
|
||||
assert(util.isNumber(options.minPriority));
|
||||
this.minPriority = options.minPriority;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate miner options from object.
|
||||
* @param {Object} options
|
||||
* @returns {MinerOptions}
|
||||
*/
|
||||
|
||||
MinerOptions.fromOptions = function fromOptions(options) {
|
||||
return new MinerOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue
|
||||
* @constructor
|
||||
|
||||
@ -8,12 +8,12 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var BN = require('bn.js');
|
||||
var util = require('../utils/util');
|
||||
var co = require('../utils/co');
|
||||
var StaticWriter = require('../utils/staticwriter');
|
||||
var Network = require('../protocol/network');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var TX = require('../primitives/tx');
|
||||
var Block = require('../primitives/block');
|
||||
var Input = require('../primitives/input');
|
||||
@ -48,6 +48,7 @@ function MinerBlock(options) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.network = Network.get(options.network);
|
||||
this.tip = options.tip;
|
||||
this.version = options.version;
|
||||
this.height = options.tip.height + 1;
|
||||
@ -61,7 +62,6 @@ function MinerBlock(options) {
|
||||
this.coinbaseFlags = options.coinbaseFlags;
|
||||
this.witness = options.witness;
|
||||
this.address = options.address;
|
||||
this.network = Network.get(options.network);
|
||||
this.reward = consensus.getReward(this.height, this.network.halvingInterval);
|
||||
|
||||
this.destroyed = false;
|
||||
|
||||
@ -308,14 +308,31 @@ BIP150.address = function address(key) {
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function AuthDB() {
|
||||
function AuthDB(options) {
|
||||
if (!(this instanceof AuthDB))
|
||||
return new AuthDB();
|
||||
return new AuthDB(options);
|
||||
|
||||
this.known = {};
|
||||
this.authorized = [];
|
||||
|
||||
this._init(options);
|
||||
}
|
||||
|
||||
AuthDB.prototype._init = function _init(options) {
|
||||
if (!options)
|
||||
return;
|
||||
|
||||
if (options.knownPeers != null) {
|
||||
assert(typeof options.knownPeers === 'object');
|
||||
this.setKnown(options.knownPeers);
|
||||
}
|
||||
|
||||
if (options.authPeers != null) {
|
||||
assert(Array.isArray(options.authPeers));
|
||||
this.setAuthorized(options.authPeers);
|
||||
}
|
||||
};
|
||||
|
||||
AuthDB.prototype.addKnown = function addKnown(host, key) {
|
||||
assert(typeof host === 'string');
|
||||
assert(Buffer.isBuffer(key) && key.length === 33,
|
||||
|
||||
@ -15,6 +15,9 @@ var List = require('../utils/list');
|
||||
var murmur3 = require('../utils/murmur3');
|
||||
var StaticWriter = require('../utils/staticwriter');
|
||||
var Map = require('../utils/map');
|
||||
var common = require('./common');
|
||||
var dns = require('./dns');
|
||||
var Network = require('../protocol/network');
|
||||
|
||||
/**
|
||||
* Host List
|
||||
@ -26,19 +29,12 @@ function HostList(options) {
|
||||
if (!(this instanceof HostList))
|
||||
return new HostList(options);
|
||||
|
||||
assert(options, 'Options are required.');
|
||||
assert(options.address);
|
||||
assert(options.network);
|
||||
assert(options.logger);
|
||||
assert(typeof options.resolve === 'function');
|
||||
assert(options.banTime >= 0);
|
||||
|
||||
this.address = options.address;
|
||||
this.network = options.network;
|
||||
this.logger = options.logger;
|
||||
this.proxyServer = options.proxyServer;
|
||||
this.resolve = options.resolve;
|
||||
this.banTime = options.banTime;
|
||||
this.network = Network.primary;
|
||||
this.logger = null;
|
||||
this.address = new NetAddress();
|
||||
this.proxyServer = null;
|
||||
this.resolve = dns.resolve;
|
||||
this.banTime = common.BAN_TIME;
|
||||
|
||||
this.seeds = [];
|
||||
this.banned = {};
|
||||
@ -60,23 +56,68 @@ function HostList(options) {
|
||||
this.maxFailures = 10;
|
||||
this.maxRefs = 8;
|
||||
|
||||
this._init();
|
||||
this._init(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize options.
|
||||
* @private
|
||||
*/
|
||||
|
||||
HostList.prototype._initOptions = function initOptions(options) {
|
||||
if (options.network != null)
|
||||
this.network = Network.get(options.network);
|
||||
|
||||
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.proxyServer != null) {
|
||||
assert(typeof options.proxyServer === 'string');
|
||||
this.proxyServer = options.proxyServer;
|
||||
}
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize list.
|
||||
* @private
|
||||
*/
|
||||
|
||||
HostList.prototype._init = function init() {
|
||||
HostList.prototype._init = function init(options) {
|
||||
var i;
|
||||
|
||||
this._initOptions(options);
|
||||
|
||||
for (i = 0; i < this.maxBuckets; i++)
|
||||
this.fresh.push(new Map());
|
||||
|
||||
for (i = 0; i < this.maxBuckets; i++)
|
||||
this.used.push(new List());
|
||||
|
||||
if (options.seeds) {
|
||||
this.setSeeds(options.seeds);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSeeds(this.network.seeds);
|
||||
};
|
||||
|
||||
@ -661,12 +702,14 @@ HostList.prototype.populate = co(function* populate(seed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('Resolving hosts from seed: %s.', seed.host);
|
||||
if (this.logger)
|
||||
this.logger.info('Resolving hosts from seed: %s.', seed.host);
|
||||
|
||||
try {
|
||||
hosts = yield this.resolve(seed.host, this.proxyServer);
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
if (this.logger)
|
||||
this.logger.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -146,7 +146,7 @@ function Peer(pool) {
|
||||
this.hostname,
|
||||
this.outbound,
|
||||
this.pool.authdb,
|
||||
this.pool.identityKey);
|
||||
this.options.identityKey);
|
||||
this.bip151.bip150 = this.bip150;
|
||||
}
|
||||
}
|
||||
@ -353,12 +353,12 @@ Peer.prototype.accept = function accept(socket) {
|
||||
*/
|
||||
|
||||
Peer.prototype.connect = function connect(addr) {
|
||||
var proxy = this.pool.proxyServer;
|
||||
var proxy = this.options.proxyServer;
|
||||
var socket;
|
||||
|
||||
assert(!this.socket);
|
||||
|
||||
socket = this.pool.createSocket(addr.port, addr.host, proxy);
|
||||
socket = this.options.createSocket(addr.port, addr.host, proxy);
|
||||
|
||||
this.address = addr;
|
||||
this.ts = util.now();
|
||||
@ -889,13 +889,13 @@ Peer.prototype.sendHeaders = function sendHeaders(items) {
|
||||
|
||||
Peer.prototype.sendVersion = function sendVersion() {
|
||||
var packet = new packets.VersionPacket();
|
||||
packet.version = this.pool.protoVersion;
|
||||
packet.services = this.pool.address.services;
|
||||
packet.version = this.options.version;
|
||||
packet.services = this.options.services;
|
||||
packet.ts = this.network.now();
|
||||
packet.recv = this.address;
|
||||
packet.from = this.pool.address;
|
||||
packet.nonce = this.pool.localNonce;
|
||||
packet.agent = this.pool.userAgent;
|
||||
packet.nonce = this.pool.nonce;
|
||||
packet.agent = this.options.agent;
|
||||
packet.height = this.chain.height;
|
||||
packet.noRelay = this.options.noRelay;
|
||||
this.send(packet);
|
||||
@ -1698,7 +1698,7 @@ Peer.prototype.handleGetUTXOs = co(function* handleGetUTXOs(packet) {
|
||||
if (this.options.selfish)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
return;
|
||||
|
||||
if (packet.prevout.length > 15)
|
||||
@ -1770,10 +1770,10 @@ Peer.prototype.handleGetHeaders = co(function* handleGetHeaders(packet) {
|
||||
if (this.options.selfish)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
return;
|
||||
|
||||
if (packet.locator.length > 0) {
|
||||
@ -1818,10 +1818,10 @@ Peer.prototype.handleGetBlocks = co(function* handleGetBlocks(packet) {
|
||||
if (this.options.selfish)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
return;
|
||||
|
||||
hash = yield this.chain.findLocator(packet.locator);
|
||||
@ -1863,7 +1863,7 @@ Peer.prototype.handleVersion = co(function* handleVersion(packet) {
|
||||
this.haveWitness = packet.hasWitness();
|
||||
|
||||
if (!this.network.selfConnect) {
|
||||
if (util.equal(packet.nonce, this.pool.localNonce))
|
||||
if (util.equal(packet.nonce, this.pool.nonce))
|
||||
throw new Error('We connected to ourself. Oops.');
|
||||
}
|
||||
|
||||
@ -1990,10 +1990,10 @@ Peer.prototype.getItem = co(function* getItem(item) {
|
||||
return this.mempool.getTX(item.hash);
|
||||
}
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
return;
|
||||
|
||||
return yield this.chain.db.getBlock(item.hash);
|
||||
@ -2016,14 +2016,14 @@ Peer.prototype.sendBlock = co(function* sendBlock(item, witness) {
|
||||
}
|
||||
|
||||
if (this.options.selfish
|
||||
|| this.chain.db.options.spv
|
||||
|| this.chain.db.options.prune) {
|
||||
|| this.chain.options.spv
|
||||
|| this.chain.options.prune) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have the same serialization, we
|
||||
// can write the raw binary to the socket.
|
||||
if (witness === this.chain.db.options.witness) {
|
||||
if (witness === this.chain.options.witness) {
|
||||
block = yield this.chain.db.getRawBlock(item.hash);
|
||||
|
||||
if (!block)
|
||||
@ -2713,10 +2713,10 @@ Peer.prototype.handleGetBlockTxn = co(function* handleGetBlockTxn(packet) {
|
||||
var req = packet.request;
|
||||
var res, item, block, height;
|
||||
|
||||
if (this.chain.db.options.spv)
|
||||
if (this.chain.options.spv)
|
||||
return;
|
||||
|
||||
if (this.chain.db.options.prune)
|
||||
if (this.chain.options.prune)
|
||||
return;
|
||||
|
||||
if (this.options.selfish)
|
||||
@ -2928,7 +2928,7 @@ Peer.prototype.sendCompact = function sendCompact() {
|
||||
Peer.prototype.increaseBan = function increaseBan(score) {
|
||||
this.banScore += score;
|
||||
|
||||
if (this.banScore >= this.pool.banScore) {
|
||||
if (this.banScore >= this.options.banScore) {
|
||||
this.logger.debug('Ban threshold exceeded (%s).', this.hostname);
|
||||
this.ban();
|
||||
return true;
|
||||
|
||||
551
lib/net/pool.js
551
lib/net/pool.js
@ -56,8 +56,6 @@ var VerifyResult = errors.VerifyResult;
|
||||
* headers, hashes, utxos, or transactions to peers.
|
||||
* @param {Boolean?} options.broadcast - Whether to automatically broadcast
|
||||
* transactions accepted to our mempool.
|
||||
* @param {Boolean?} options.witness - Request witness blocks and transactions.
|
||||
* Only deal with witness peers.
|
||||
* @param {Boolean} options.noDiscovery - Automatically discover new
|
||||
* peers.
|
||||
* @param {String[]} options.seeds
|
||||
@ -94,198 +92,46 @@ function Pool(options) {
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
assert(options && options.chain, 'Pool requires a blockchain.');
|
||||
this.options = new PoolOptions(options);
|
||||
|
||||
this.options = options;
|
||||
this.chain = options.chain;
|
||||
this.logger = options.logger || this.chain.logger;
|
||||
this.mempool = options.mempool;
|
||||
this.network = this.chain.network;
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.chain = this.options.chain;
|
||||
this.mempool = this.options.mempool;
|
||||
this.server = this.options.createServer();
|
||||
this.locker = new Lock();
|
||||
|
||||
this.server = null;
|
||||
this.maxOutbound = 8;
|
||||
this.maxInbound = 8;
|
||||
this.connected = false;
|
||||
this.syncing = false;
|
||||
this.createSocket = tcp.createSocket;
|
||||
this.createServer = tcp.createServer;
|
||||
this.resolve = dns.resolve;
|
||||
this.locker = new Lock();
|
||||
this.authdb = null;
|
||||
this.identityKey = null;
|
||||
this.proxyServer = null;
|
||||
this.banTime = common.BAN_TIME;
|
||||
this.banScore = common.BAN_SCORE;
|
||||
this.feeRate = -1;
|
||||
|
||||
// Required services.
|
||||
this.reqServices = common.REQUIRED_SERVICES;
|
||||
|
||||
this.address = new NetAddress();
|
||||
this.address.ts = this.network.now();
|
||||
this.address.services = common.LOCAL_SERVICES;
|
||||
this.address.setPort(this.network.port);
|
||||
|
||||
this.hosts = new HostList(this);
|
||||
this.peers = new PeerList(this);
|
||||
|
||||
this.localNonce = util.nonce();
|
||||
|
||||
this.protoVersion = common.PROTOCOL_VERSION;
|
||||
this.userAgent = common.USER_AGENT;
|
||||
|
||||
this.feeRate = this.options.feeRate;
|
||||
this.nonce = util.nonce();
|
||||
this.spvFilter = null;
|
||||
this.txFilter = null;
|
||||
|
||||
// Requested objects.
|
||||
this.requestMap = new Map();
|
||||
this.queueMap = new Map();
|
||||
|
||||
// Currently broadcasted objects.
|
||||
this.invMap = new Map();
|
||||
this.invTimeout = 60000;
|
||||
|
||||
this.scheduled = false;
|
||||
this.pendingWatch = null;
|
||||
this.pendingRefill = null;
|
||||
|
||||
this._initOptions();
|
||||
this._init();
|
||||
};
|
||||
this.peers = new PeerList();
|
||||
this.authdb = new BIP150.AuthDB(this.options);
|
||||
this.address = new NetAddress(this.options);
|
||||
this.address.ts = this.network.now();
|
||||
this.hosts = new HostList(this.options);
|
||||
this.hosts.address = this.address;
|
||||
|
||||
util.inherits(Pool, AsyncObject);
|
||||
|
||||
/**
|
||||
* Initialize options.
|
||||
* @private
|
||||
*/
|
||||
|
||||
Pool.prototype._initOptions = function _initOptions() {
|
||||
if (this.options.noRelay == null)
|
||||
this.options.noRelay = !!this.options.spv;
|
||||
|
||||
if (this.options.headers == null)
|
||||
this.options.headers = this.options.spv;
|
||||
|
||||
if (!this.options.witness) {
|
||||
this.address.services &= ~common.services.WITNESS;
|
||||
this.reqServices &= ~common.services.WITNESS;
|
||||
}
|
||||
|
||||
if (this.options.host != null) {
|
||||
assert(typeof this.options.host === 'string');
|
||||
this.address.setHost(this.options.host);
|
||||
}
|
||||
|
||||
if (this.options.port != null) {
|
||||
assert(typeof this.options.port === 'number');
|
||||
this.address.setPort(this.options.port);
|
||||
}
|
||||
|
||||
if (this.options.maxOutbound != null) {
|
||||
assert(typeof this.options.maxOutbound === 'number');
|
||||
this.maxOutbound = this.options.maxOutbound;
|
||||
}
|
||||
|
||||
if (this.options.maxInbound != null) {
|
||||
assert(typeof this.options.maxInbound === 'number');
|
||||
this.maxInbound = this.options.maxInbound;
|
||||
}
|
||||
|
||||
if (this.options.createSocket) {
|
||||
assert(typeof this.options.createSocket === 'function');
|
||||
this.createSocket = this.options.createSocket;
|
||||
}
|
||||
|
||||
if (this.options.createServer) {
|
||||
assert(typeof this.options.createServer === 'function');
|
||||
this.createServer = this.options.createServer;
|
||||
}
|
||||
|
||||
if (this.options.resolve) {
|
||||
assert(typeof this.options.resolve === 'function');
|
||||
this.resolve = this.options.resolve;
|
||||
}
|
||||
|
||||
if (this.options.proxyServer) {
|
||||
assert(typeof this.options.proxyServer === 'string');
|
||||
this.proxyServer = this.options.proxyServer;
|
||||
}
|
||||
|
||||
if (this.options.selfish) {
|
||||
assert(typeof this.options.selfish === 'boolean');
|
||||
this.address.services &= ~common.services.NETWORK;
|
||||
}
|
||||
|
||||
if (this.options.spv) {
|
||||
assert(typeof this.options.spv === 'boolean');
|
||||
this.address.services &= ~common.services.NETWORK;
|
||||
}
|
||||
|
||||
if (this.options.protoVersion) {
|
||||
assert(typeof this.options.protoVersion === 'number');
|
||||
this.protoVersion = this.options.protoVersion;
|
||||
}
|
||||
|
||||
if (this.options.userAgent) {
|
||||
assert(typeof this.options.userAgent === 'string');
|
||||
assert(this.options.userAgent.length < 256);
|
||||
this.userAgent = this.options.userAgent;
|
||||
}
|
||||
|
||||
if (this.options.bip150) {
|
||||
assert(typeof this.options.bip151 === 'boolean');
|
||||
|
||||
this.authdb = new BIP150.AuthDB();
|
||||
|
||||
if (this.options.authPeers)
|
||||
this.authdb.setAuthorized(this.options.authPeers);
|
||||
|
||||
if (this.options.knownPeers)
|
||||
this.authdb.setKnown(this.options.knownPeers);
|
||||
|
||||
this.identityKey = this.options.identityKey || ec.generatePrivateKey();
|
||||
|
||||
assert(Buffer.isBuffer(this.identityKey), 'Identity key must be a buffer.');
|
||||
assert(ec.privateKeyVerify(this.identityKey),
|
||||
'Invalid identity key.');
|
||||
}
|
||||
|
||||
if (this.options.banScore != null) {
|
||||
assert(typeof this.options.banScore === 'number');
|
||||
this.banScore = this.options.banScore;
|
||||
}
|
||||
|
||||
if (this.options.banTime != null) {
|
||||
assert(typeof this.options.banTime === 'number');
|
||||
this.banTime = this.options.banTime;
|
||||
}
|
||||
|
||||
if (this.options.feeRate != null) {
|
||||
assert(typeof this.options.feeRate === 'number');
|
||||
this.feeRate = this.options.feeRate;
|
||||
}
|
||||
|
||||
if (this.options.seeds)
|
||||
this.hosts.setSeeds(this.options.seeds);
|
||||
|
||||
if (this.options.preferredSeed)
|
||||
this.hosts.setSeeds([this.options.preferredSeed]);
|
||||
|
||||
if (this.options.spv) {
|
||||
if (this.options.spv)
|
||||
this.spvFilter = Bloom.fromRate(10000, 0.001, Bloom.flags.ALL);
|
||||
this.reqServices |= common.services.BLOOM;
|
||||
}
|
||||
|
||||
if (!this.options.mempool)
|
||||
this.txFilter = new Bloom.Rolling(50000, 0.000001);
|
||||
|
||||
if (this.options.invTimeout != null) {
|
||||
assert(typeof this.options.invTimeout === 'number');
|
||||
this.invTimeout = this.options.invTimeout;
|
||||
}
|
||||
this._init();
|
||||
};
|
||||
|
||||
util.inherits(Pool, AsyncObject);
|
||||
|
||||
/**
|
||||
* Initialize the pool.
|
||||
* @private
|
||||
@ -294,7 +140,22 @@ Pool.prototype._initOptions = function _initOptions() {
|
||||
Pool.prototype._init = function _init() {
|
||||
var self = this;
|
||||
|
||||
this._initServer();
|
||||
this.server.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.server.on('connection', function(socket) {
|
||||
self.handleSocket(socket);
|
||||
self.emit('connection', socket);
|
||||
});
|
||||
|
||||
this.server.on('listening', function() {
|
||||
var data = self.server.address();
|
||||
self.logger.info(
|
||||
'Pool server listening on %s (port=%d).',
|
||||
data.address, data.port);
|
||||
self.emit('listening', data);
|
||||
});
|
||||
|
||||
this.chain.on('block', function(block, entry) {
|
||||
self.emit('block', block, entry);
|
||||
@ -351,39 +212,6 @@ Pool.prototype._init = function _init() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize server.
|
||||
* @private
|
||||
*/
|
||||
|
||||
Pool.prototype._initServer = function _initServer() {
|
||||
var self = this;
|
||||
|
||||
assert(!this.server);
|
||||
|
||||
if (!this.createServer)
|
||||
return;
|
||||
|
||||
this.server = this.createServer();
|
||||
|
||||
this.server.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.server.on('connection', function(socket) {
|
||||
self.handleSocket(socket);
|
||||
self.emit('connection', socket);
|
||||
});
|
||||
|
||||
this.server.on('listening', function() {
|
||||
var data = self.server.address();
|
||||
self.logger.info(
|
||||
'Pool server listening on %s (port=%d).',
|
||||
data.address, data.port);
|
||||
self.emit('listening', data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the pool, wait for the chain to load.
|
||||
* @alias Pool#open
|
||||
@ -398,7 +226,7 @@ Pool.prototype._open = co(function* _open() {
|
||||
else
|
||||
yield this.chain.open();
|
||||
|
||||
this.logger.info('Pool loaded (maxpeers=%d).', this.maxOutbound);
|
||||
this.logger.info('Pool loaded (maxpeers=%d).', this.options.maxOutbound);
|
||||
|
||||
if (this.identityKey) {
|
||||
key = ec.publicKeyCreate(this.identityKey, true);
|
||||
@ -533,9 +361,6 @@ Pool.prototype._disconnect = co(function* disconnect() {
|
||||
*/
|
||||
|
||||
Pool.prototype.listen = co(function* listen() {
|
||||
if (!this.createServer)
|
||||
return;
|
||||
|
||||
assert(this.server);
|
||||
assert(!this.connected, 'Already listening.');
|
||||
|
||||
@ -565,9 +390,6 @@ Pool.prototype._listen = function _listen() {
|
||||
*/
|
||||
|
||||
Pool.prototype.unlisten = co(function* unlisten() {
|
||||
if (!this.createServer)
|
||||
return;
|
||||
|
||||
assert(this.server);
|
||||
assert(this.connected, 'Not listening.');
|
||||
|
||||
@ -607,7 +429,7 @@ Pool.prototype.handleSocket = function handleSocket(socket) {
|
||||
|
||||
host = IP.normalize(socket.remoteAddress);
|
||||
|
||||
if (this.peers.inbound >= this.maxInbound) {
|
||||
if (this.peers.inbound >= this.options.maxInbound) {
|
||||
this.logger.debug('Ignoring peer: too many inbound (%s).', host);
|
||||
socket.destroy();
|
||||
return;
|
||||
@ -966,9 +788,13 @@ Pool.prototype.handleOpen = function handleOpen(peer) {
|
||||
|
||||
Pool.prototype.handleClose = co(function* handleClose(peer, connected) {
|
||||
var outbound = peer.outbound;
|
||||
var loader = peer.isLoader();
|
||||
|
||||
this.removePeer(peer);
|
||||
|
||||
if (loader)
|
||||
this.logger.info('Removed loader peer (%s).', peer.hostname);
|
||||
|
||||
if (!this.loaded)
|
||||
return;
|
||||
|
||||
@ -1007,6 +833,7 @@ Pool.prototype.handleVersion = function handleVersion(peer, packet) {
|
||||
*/
|
||||
|
||||
Pool.prototype.handleAddr = function handleAddr(peer, addrs) {
|
||||
var services = this.options.requiredServices;
|
||||
var i, addr;
|
||||
|
||||
if (this.options.noDiscovery)
|
||||
@ -1018,7 +845,7 @@ Pool.prototype.handleAddr = function handleAddr(peer, addrs) {
|
||||
if (!addr.isRoutable())
|
||||
continue;
|
||||
|
||||
if (!addr.hasServices(this.reqServices))
|
||||
if (!addr.hasServices(services))
|
||||
continue;
|
||||
|
||||
if (this.hosts.add(addr, peer.address))
|
||||
@ -1489,6 +1316,7 @@ Pool.prototype.addInbound = function addInbound(socket) {
|
||||
*/
|
||||
|
||||
Pool.prototype.getHost = function getHost(unique) {
|
||||
var services = this.options.requiredServices;
|
||||
var now = this.network.now();
|
||||
var i, entry, addr;
|
||||
|
||||
@ -1508,7 +1336,7 @@ Pool.prototype.getHost = function getHost(unique) {
|
||||
if (!addr.isValid())
|
||||
continue;
|
||||
|
||||
if (!addr.hasServices(this.reqServices))
|
||||
if (!addr.hasServices(services))
|
||||
continue;
|
||||
|
||||
if (i < 30 && now - entry.lastAttempt < 600)
|
||||
@ -1536,7 +1364,7 @@ Pool.prototype.addOutbound = function addOutbound() {
|
||||
if (!this.loaded)
|
||||
return;
|
||||
|
||||
if (this.peers.outbound >= this.maxOutbound)
|
||||
if (this.peers.outbound >= this.options.maxOutbound)
|
||||
return;
|
||||
|
||||
// Hang back if we don't have a loader peer yet.
|
||||
@ -1561,7 +1389,7 @@ Pool.prototype.addOutbound = function addOutbound() {
|
||||
*/
|
||||
|
||||
Pool.prototype.fillOutbound = function fillOutbound() {
|
||||
var need = this.maxOutbound - this.peers.outbound;
|
||||
var need = this.options.maxOutbound - this.peers.outbound;
|
||||
var i;
|
||||
|
||||
if (!this.peers.load)
|
||||
@ -1572,7 +1400,7 @@ Pool.prototype.fillOutbound = function fillOutbound() {
|
||||
|
||||
this.logger.debug('Refilling peers (%d/%d).',
|
||||
this.peers.outbound,
|
||||
this.maxOutbound);
|
||||
this.options.maxOutbound);
|
||||
|
||||
for (i = 0; i < need; i++)
|
||||
this.addOutbound();
|
||||
@ -2085,14 +1913,287 @@ Pool.prototype.getIP2 = co(function* getIP2() {
|
||||
return IP.normalize(ip);
|
||||
});
|
||||
|
||||
/**
|
||||
* PoolOptions
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function PoolOptions(options) {
|
||||
if (!(this instanceof PoolOptions))
|
||||
return new PoolOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = null;
|
||||
this.chain = null;
|
||||
this.mempool = null;
|
||||
|
||||
this.witness = false;
|
||||
this.spv = false;
|
||||
this.listen = false;
|
||||
this.headers = false;
|
||||
this.compact = false;
|
||||
this.noRelay = false;
|
||||
this.host = '0.0.0.0';
|
||||
this.port = this.network.port;
|
||||
this.maxOutbound = 8;
|
||||
this.maxInbound = 8;
|
||||
this.createSocket = tcp.createSocket;
|
||||
this.createServer = tcp.createServer;
|
||||
this.resolve = dns.resolve;
|
||||
this.proxyServer = null;
|
||||
this.selfish = false;
|
||||
this.version = common.PROTOCOL_VERSION;
|
||||
this.agent = common.USER_AGENT;
|
||||
this.bip151 = false;
|
||||
this.bip150 = false;
|
||||
this.authPeers = [];
|
||||
this.knownPeers = {};
|
||||
this.identityKey = ec.generatePrivateKey();
|
||||
this.banScore = common.BAN_SCORE;
|
||||
this.banTime = common.BAN_TIME;
|
||||
this.feeRate = -1;
|
||||
this.noDiscovery = false;
|
||||
this.seeds = this.network.seeds;
|
||||
this.preferredSeed = null;
|
||||
this.invTimeout = 60000;
|
||||
this.services = common.LOCAL_SERVICES;
|
||||
this.requiredServices = common.REQUIRED_SERVICES;
|
||||
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {PoolOptions}
|
||||
*/
|
||||
|
||||
PoolOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
assert(options, 'Pool requires options.');
|
||||
assert(options.chain && typeof options.chain === 'object',
|
||||
'Pool options require a blockchain.');
|
||||
|
||||
this.chain = options.chain;
|
||||
this.network = options.chain.network;
|
||||
this.logger = options.chain.logger;
|
||||
|
||||
this.port = this.network.port;
|
||||
this.seeds = this.network.seeds;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.mempool != null) {
|
||||
assert(typeof options.mempool === 'object');
|
||||
this.mempool = options.mempool;
|
||||
}
|
||||
|
||||
if (options.witness != null) {
|
||||
assert(typeof options.witness === 'boolean');
|
||||
assert(options.witness === this.chain.options.witness);
|
||||
this.witness = options.witness;
|
||||
} else {
|
||||
this.witness = this.chain.options.witness;
|
||||
}
|
||||
|
||||
if (options.spv != null) {
|
||||
assert(typeof options.spv === 'boolean');
|
||||
assert(options.spv === this.chain.options.spv);
|
||||
this.spv = options.spv;
|
||||
} else {
|
||||
this.spv = this.chain.options.spv;
|
||||
}
|
||||
|
||||
if (options.listen != null) {
|
||||
assert(typeof options.listen === 'boolean');
|
||||
this.listen = options.listen;
|
||||
}
|
||||
|
||||
if (options.headers != null) {
|
||||
assert(typeof options.headers === 'boolean');
|
||||
this.headers = options.headers;
|
||||
} else {
|
||||
this.headers = this.spv === true;
|
||||
}
|
||||
|
||||
if (options.compact != null) {
|
||||
assert(typeof options.compact === 'boolean');
|
||||
this.compact = options.compact;
|
||||
}
|
||||
|
||||
if (options.noRelay != null) {
|
||||
assert(typeof options.noRelay === 'boolean');
|
||||
this.noRelay = options.noRelay;
|
||||
} else {
|
||||
this.noRelay = this.spv === true;
|
||||
}
|
||||
|
||||
if (options.host != null) {
|
||||
assert(typeof options.host === 'string');
|
||||
this.host = options.host;
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
assert(typeof options.port === 'number');
|
||||
this.port = options.port;
|
||||
}
|
||||
|
||||
if (options.maxOutbound != null) {
|
||||
assert(typeof options.maxOutbound === 'number');
|
||||
this.maxOutbound = options.maxOutbound;
|
||||
}
|
||||
|
||||
if (options.maxInbound != null) {
|
||||
assert(typeof options.maxInbound === 'number');
|
||||
this.maxInbound = options.maxInbound;
|
||||
}
|
||||
|
||||
if (options.createSocket) {
|
||||
assert(typeof options.createSocket === 'function');
|
||||
this.createSocket = options.createSocket;
|
||||
}
|
||||
|
||||
if (options.createServer) {
|
||||
assert(typeof options.createServer === 'function');
|
||||
this.createServer = options.createServer;
|
||||
}
|
||||
|
||||
if (options.resolve) {
|
||||
assert(typeof options.resolve === 'function');
|
||||
this.resolve = options.resolve;
|
||||
}
|
||||
|
||||
if (options.proxyServer) {
|
||||
assert(typeof options.proxyServer === 'string');
|
||||
this.proxyServer = options.proxyServer;
|
||||
}
|
||||
|
||||
if (options.selfish) {
|
||||
assert(typeof options.selfish === 'boolean');
|
||||
this.selfish = options.selfish;
|
||||
}
|
||||
|
||||
if (options.version) {
|
||||
assert(typeof options.version === 'number');
|
||||
this.version = options.version;
|
||||
}
|
||||
|
||||
if (options.agent) {
|
||||
assert(typeof options.agent === 'string');
|
||||
assert(options.agent.length <= 255);
|
||||
this.agent = options.agent;
|
||||
}
|
||||
|
||||
if (options.bip151 != null) {
|
||||
assert(typeof options.bip151 === 'boolean');
|
||||
this.bip151 = options.bip151;
|
||||
}
|
||||
|
||||
if (options.bip150 != null) {
|
||||
assert(typeof options.bip150 === 'boolean');
|
||||
assert(this.bip151, 'Cannot enable bip150 without bip151.');
|
||||
|
||||
if (options.knownPeers) {
|
||||
assert(typeof options.knownPeers === 'object');
|
||||
assert(!Array.isArray(options.knownPeers));
|
||||
this.knownPeers = options.knownPeers;
|
||||
}
|
||||
|
||||
if (options.authPeers) {
|
||||
assert(Array.isArray(options.authPeers));
|
||||
this.authPeers = options.authPeers;
|
||||
}
|
||||
|
||||
if (options.identityKey) {
|
||||
assert(Buffer.isBuffer(options.identityKey),
|
||||
'Identity key must be a buffer.');
|
||||
assert(ec.privateKeyVerify(options.identityKey),
|
||||
'Invalid identity key.');
|
||||
this.identityKey = options.identityKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.banScore != null) {
|
||||
assert(typeof this.options.banScore === 'number');
|
||||
this.banScore = this.options.banScore;
|
||||
}
|
||||
|
||||
if (options.banTime != null) {
|
||||
assert(typeof this.options.banTime === 'number');
|
||||
this.banTime = this.options.banTime;
|
||||
}
|
||||
|
||||
if (options.feeRate != null) {
|
||||
assert(typeof this.options.feeRate === 'number');
|
||||
this.feeRate = this.options.feeRate;
|
||||
}
|
||||
|
||||
if (options.noDiscovery != null) {
|
||||
assert(typeof options.noDiscovery === 'boolean');
|
||||
this.noDiscovery = options.noDiscovery;
|
||||
}
|
||||
|
||||
if (options.seeds) {
|
||||
assert(Array.isArray(options.seeds));
|
||||
this.seeds = options.seeds;
|
||||
}
|
||||
|
||||
if (options.preferredSeed) {
|
||||
assert(typeof options.preferredSeed === 'string');
|
||||
this.seeds = [options.preferredSeed];
|
||||
}
|
||||
|
||||
if (options.invTimeout != null) {
|
||||
assert(typeof options.invTimeout === 'number');
|
||||
this.invTimeout = options.invTimeout;
|
||||
}
|
||||
|
||||
if (!this.witness) {
|
||||
this.services &= ~common.services.WITNESS;
|
||||
this.requiredServices &= ~common.services.WITNESS;
|
||||
}
|
||||
|
||||
if (this.spv) {
|
||||
this.requiredServices |= common.services.BLOOM;
|
||||
this.services &= ~common.services.NETWORK;
|
||||
}
|
||||
|
||||
if (this.selfish)
|
||||
this.services &= ~common.services.NETWORK;
|
||||
|
||||
if (options.services != null) {
|
||||
assert(util.isUInt32(options.services));
|
||||
this.services = options.services;
|
||||
}
|
||||
|
||||
if (options.requiredServices != null) {
|
||||
assert(util.isUInt32(options.requiredServices));
|
||||
this.requiredServices = options.requiredServices;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate options from object.
|
||||
* @param {Object} options
|
||||
* @returns {PoolOptions}
|
||||
*/
|
||||
|
||||
PoolOptions.fromOptions = function fromOptions(options) {
|
||||
return new PoolOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Peer List
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function PeerList(options) {
|
||||
this.logger = options.logger;
|
||||
function PeerList() {
|
||||
this.map = {};
|
||||
this.list = new List();
|
||||
this.load = null;
|
||||
@ -2155,10 +2256,8 @@ PeerList.prototype.remove = function remove(peer) {
|
||||
assert(this.map[peer.hostname]);
|
||||
delete this.map[peer.hostname];
|
||||
|
||||
if (peer.isLoader()) {
|
||||
this.logger.info('Removed loader peer (%s).', peer.hostname);
|
||||
if (peer.isLoader())
|
||||
this.load = null;
|
||||
}
|
||||
|
||||
if (peer.outbound)
|
||||
this.outbound--;
|
||||
@ -2293,7 +2392,7 @@ BroadcastItem.prototype.refresh = function refresh() {
|
||||
this.timeout = setTimeout(function() {
|
||||
self.emit('timeout');
|
||||
self.reject(new Error('Timed out.'));
|
||||
}, this.pool.invTimeout);
|
||||
}, this.pool.options.invTimeout);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -7,10 +7,27 @@
|
||||
'use strict';
|
||||
|
||||
var ProxySocket = require('./proxysocket');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var tcp = exports;
|
||||
|
||||
tcp.createSocket = function createSocket(port, host, proxy) {
|
||||
return ProxySocket.connect(proxy, port, host);
|
||||
};
|
||||
|
||||
tcp.createServer = null;
|
||||
tcp.createServer = function createServer() {
|
||||
var server = new EventEmitter();
|
||||
server.listen = function listen(port, host, callback) {
|
||||
callback();
|
||||
server.emit('listening');
|
||||
};
|
||||
server.close = function close(callback) {
|
||||
callback();
|
||||
};
|
||||
server.address = function address() {
|
||||
return {
|
||||
address: '127.0.0.1',
|
||||
port: 0
|
||||
};
|
||||
};
|
||||
return server;
|
||||
};
|
||||
|
||||
@ -65,17 +65,15 @@ function FullNode(options) {
|
||||
logger: this.logger,
|
||||
db: this.options.db,
|
||||
location: this.location('chain'),
|
||||
preload: false,
|
||||
spv: false,
|
||||
maxFiles: this.options.maxFiles,
|
||||
cacheSize: this.options.cacheSize,
|
||||
witness: this.options.witness,
|
||||
forceWitness: this.options.forceWitness,
|
||||
prune: this.options.prune,
|
||||
useCheckpoints: this.options.useCheckpoints,
|
||||
coinCache: this.options.coinCache,
|
||||
indexTX: this.options.indexTX,
|
||||
indexAddress: this.options.indexAddress,
|
||||
maxFiles: this.options.maxFiles,
|
||||
cacheSize: this.options.cacheSize
|
||||
indexAddress: this.options.indexAddress
|
||||
});
|
||||
|
||||
// Fee estimation.
|
||||
@ -104,7 +102,6 @@ function FullNode(options) {
|
||||
logger: this.logger,
|
||||
chain: this.chain,
|
||||
mempool: this.mempool,
|
||||
witness: this.options.witness,
|
||||
selfish: this.options.selfish,
|
||||
headers: this.options.headers,
|
||||
compact: this.options.compact,
|
||||
@ -119,8 +116,7 @@ function FullNode(options) {
|
||||
preferredSeed: this.options.preferredSeed,
|
||||
noDiscovery: this.options.noDiscovery,
|
||||
port: this.options.port,
|
||||
listen: this.options.listen,
|
||||
spv: false
|
||||
listen: this.options.listen
|
||||
});
|
||||
|
||||
// Miner needs access to the chain and mempool.
|
||||
@ -141,13 +137,12 @@ function FullNode(options) {
|
||||
client: this.client,
|
||||
db: this.options.db,
|
||||
location: this.location('walletdb'),
|
||||
witness: false,
|
||||
useCheckpoints: this.options.useCheckpoints,
|
||||
maxFiles: this.options.walletMaxFiles,
|
||||
cacheSize: this.options.walletCacheSize,
|
||||
witness: false,
|
||||
useCheckpoints: this.options.useCheckpoints,
|
||||
startHeight: this.options.startHeight,
|
||||
wipeNoReally: this.options.wipeNoReally,
|
||||
resolution: false,
|
||||
verify: false
|
||||
});
|
||||
|
||||
@ -159,8 +154,8 @@ function FullNode(options) {
|
||||
node: this,
|
||||
key: this.options.sslKey,
|
||||
cert: this.options.sslCert,
|
||||
port: this.options.httpPort || this.network.rpcPort,
|
||||
host: this.options.httpHost || '0.0.0.0',
|
||||
port: this.options.httpPort,
|
||||
host: this.options.httpHost,
|
||||
apiKey: this.options.apiKey,
|
||||
serviceKey: this.options.serviceKey,
|
||||
walletAuth: this.options.walletAuth,
|
||||
|
||||
@ -23,54 +23,105 @@ function Logger(options) {
|
||||
if (!(this instanceof Logger))
|
||||
return new Logger(options);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
if (typeof options === 'string')
|
||||
options = { level: options };
|
||||
|
||||
this.level = Logger.levels.warning;
|
||||
this.colors = options.colors !== false;
|
||||
this.console = options.console !== false;
|
||||
this.file = options.file;
|
||||
this.stream = options.stream;
|
||||
this.level = Logger.levels.WARNING;
|
||||
this.colors = Logger.HAS_TTY;
|
||||
this.console = true;
|
||||
this.file = null;
|
||||
this.stream = null;
|
||||
this.closed = false;
|
||||
|
||||
assert(!this.file || typeof this.file === 'string', 'Bad file.');
|
||||
assert(!this.stream || typeof this.stream.write === 'function', 'Bad stream.');
|
||||
|
||||
if (!process.stdout || !process.stdout.isTTY)
|
||||
this.colors = false;
|
||||
|
||||
if (options.level != null)
|
||||
this.setLevel(options.level);
|
||||
this._init(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether stdout is a tty FD.
|
||||
* @const {Boolean}
|
||||
*/
|
||||
|
||||
Logger.HAS_TTY = !!(process.stdout && process.stdout.isTTY);
|
||||
|
||||
/**
|
||||
* Available log levels.
|
||||
* @enum {Number}
|
||||
*/
|
||||
|
||||
Logger.levels = {
|
||||
none: 0,
|
||||
error: 1,
|
||||
warning: 2,
|
||||
info: 3,
|
||||
debug: 4,
|
||||
spam: 5
|
||||
NONE: 0,
|
||||
ERROR: 1,
|
||||
WARNING: 2,
|
||||
INFO: 3,
|
||||
DEBUG: 4,
|
||||
SPAM: 5
|
||||
};
|
||||
|
||||
/**
|
||||
* Available log levels.
|
||||
* @enum {Number}
|
||||
*/
|
||||
|
||||
Logger.levelsByVal = [
|
||||
'none',
|
||||
'error',
|
||||
'warning',
|
||||
'info',
|
||||
'debug',
|
||||
'spam'
|
||||
];
|
||||
|
||||
/**
|
||||
* Default CSI colors.
|
||||
* @enum {String}
|
||||
*/
|
||||
|
||||
Logger.colors = {
|
||||
error: '1;31',
|
||||
warning: '1;33',
|
||||
info: '94',
|
||||
debug: '90',
|
||||
spam: '90'
|
||||
Logger.colors = [
|
||||
'0',
|
||||
'1;31',
|
||||
'1;33',
|
||||
'94',
|
||||
'90',
|
||||
'90'
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize the logger.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
Logger.prototype._init = function _init(options) {
|
||||
if (!options)
|
||||
return;
|
||||
|
||||
if (typeof options === 'string') {
|
||||
this.setLevel(options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.level != null) {
|
||||
assert(typeof options.level === 'string');
|
||||
this.setLevel(options.level);
|
||||
}
|
||||
|
||||
if (options.colors != null && Logger.HAS_TTY) {
|
||||
assert(typeof options.colors === 'boolean');
|
||||
this.colors = options.colors;
|
||||
}
|
||||
|
||||
if (options.console != null) {
|
||||
assert(typeof options.console === 'boolean');
|
||||
this.console = options.console;
|
||||
}
|
||||
|
||||
if (options.file != null) {
|
||||
assert(typeof options.file === 'string', 'Bad file.');
|
||||
this.file = options.file;
|
||||
}
|
||||
|
||||
if (options.stream != null) {
|
||||
assert(typeof options.stream === 'object', 'Bad stream.');
|
||||
assert(typeof options.stream.write === 'function', 'Bad stream.');
|
||||
this.stream = options.stream;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -105,8 +156,8 @@ Logger.prototype.close = function close() {
|
||||
* @param {String} level
|
||||
*/
|
||||
|
||||
Logger.prototype.setLevel = function setLevel(level) {
|
||||
level = Logger.levels[level];
|
||||
Logger.prototype.setLevel = function setLevel(name) {
|
||||
var level = Logger.levels[name.toUpperCase()];
|
||||
assert(level != null, 'Invalid log level.');
|
||||
this.level = level;
|
||||
};
|
||||
@ -120,7 +171,7 @@ Logger.prototype.setLevel = function setLevel(level) {
|
||||
Logger.prototype.error = function error(err) {
|
||||
var i, args;
|
||||
|
||||
if (this.level < Logger.levels.error)
|
||||
if (this.level < Logger.levels.ERROR)
|
||||
return;
|
||||
|
||||
if (err instanceof Error)
|
||||
@ -131,7 +182,7 @@ Logger.prototype.error = function error(err) {
|
||||
for (i = 0; i < args.length; i++)
|
||||
args[i] = arguments[i];
|
||||
|
||||
this.log('error', args);
|
||||
this.log(Logger.levels.ERROR, args);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -143,7 +194,7 @@ Logger.prototype.error = function error(err) {
|
||||
Logger.prototype.warning = function warning() {
|
||||
var i, args;
|
||||
|
||||
if (this.level < Logger.levels.warning)
|
||||
if (this.level < Logger.levels.WARNING)
|
||||
return;
|
||||
|
||||
args = new Array(arguments.length);
|
||||
@ -151,7 +202,7 @@ Logger.prototype.warning = function warning() {
|
||||
for (i = 0; i < args.length; i++)
|
||||
args[i] = arguments[i];
|
||||
|
||||
this.log('warning', args);
|
||||
this.log(Logger.levels.WARNING, args);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -163,7 +214,7 @@ Logger.prototype.warning = function warning() {
|
||||
Logger.prototype.info = function info() {
|
||||
var i, args;
|
||||
|
||||
if (this.level < Logger.levels.info)
|
||||
if (this.level < Logger.levels.INFO)
|
||||
return;
|
||||
|
||||
args = new Array(arguments.length);
|
||||
@ -171,7 +222,7 @@ Logger.prototype.info = function info() {
|
||||
for (i = 0; i < args.length; i++)
|
||||
args[i] = arguments[i];
|
||||
|
||||
this.log('info', args);
|
||||
this.log(Logger.levels.INFO, args);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -183,7 +234,7 @@ Logger.prototype.info = function info() {
|
||||
Logger.prototype.debug = function debug() {
|
||||
var i, args;
|
||||
|
||||
if (this.level < Logger.levels.debug)
|
||||
if (this.level < Logger.levels.DEBUG)
|
||||
return;
|
||||
|
||||
args = new Array(arguments.length);
|
||||
@ -191,7 +242,7 @@ Logger.prototype.debug = function debug() {
|
||||
for (i = 0; i < args.length; i++)
|
||||
args[i] = arguments[i];
|
||||
|
||||
this.log('debug', args);
|
||||
this.log(Logger.levels.DEBUG, args);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -203,7 +254,7 @@ Logger.prototype.debug = function debug() {
|
||||
Logger.prototype.spam = function spam() {
|
||||
var i, args;
|
||||
|
||||
if (this.level < Logger.levels.spam)
|
||||
if (this.level < Logger.levels.SPAM)
|
||||
return;
|
||||
|
||||
args = new Array(arguments.length);
|
||||
@ -211,7 +262,7 @@ Logger.prototype.spam = function spam() {
|
||||
for (i = 0; i < args.length; i++)
|
||||
args[i] = arguments[i];
|
||||
|
||||
this.log('spam', args);
|
||||
this.log(Logger.levels.SPAM, args);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -225,7 +276,8 @@ Logger.prototype.log = function log(level, args) {
|
||||
if (this.closed)
|
||||
return;
|
||||
|
||||
assert(Logger.levels[level] != null, 'Invalid log level.');
|
||||
if (this.level < level)
|
||||
return;
|
||||
|
||||
this.writeConsole(level, args);
|
||||
this.writeStream(level, args);
|
||||
@ -238,12 +290,15 @@ Logger.prototype.log = function log(level, args) {
|
||||
*/
|
||||
|
||||
Logger.prototype.writeConsole = function writeConsole(level, args) {
|
||||
var name = Logger.levelsByVal[level];
|
||||
var prefix, msg, color;
|
||||
|
||||
assert(name, 'Invalid log level.');
|
||||
|
||||
if (!this.console)
|
||||
return;
|
||||
|
||||
prefix = '[' + level + '] ';
|
||||
prefix = '[' + name + '] ';
|
||||
|
||||
if (util.isBrowser) {
|
||||
msg = typeof args[0] !== 'object'
|
||||
@ -252,7 +307,7 @@ Logger.prototype.writeConsole = function writeConsole(level, args) {
|
||||
|
||||
msg = prefix + msg;
|
||||
|
||||
return level === 'error'
|
||||
return level === Logger.levels.ERROR
|
||||
? console.error(msg)
|
||||
: console.log(msg);
|
||||
}
|
||||
@ -264,7 +319,7 @@ Logger.prototype.writeConsole = function writeConsole(level, args) {
|
||||
|
||||
msg = prefix + util.format(args, this.colors);
|
||||
|
||||
return level === 'error'
|
||||
return level === Logger.levels.ERROR
|
||||
? process.stderr.write(msg + '\n')
|
||||
: process.stdout.write(msg + '\n');
|
||||
};
|
||||
@ -276,8 +331,11 @@ Logger.prototype.writeConsole = function writeConsole(level, args) {
|
||||
*/
|
||||
|
||||
Logger.prototype.writeStream = function writeStream(level, args) {
|
||||
var name = Logger.levelsByVal[level];
|
||||
var prefix, msg;
|
||||
|
||||
assert(name, 'Invalid log level.');
|
||||
|
||||
if (this.closed)
|
||||
return;
|
||||
|
||||
@ -294,7 +352,7 @@ Logger.prototype.writeStream = function writeStream(level, args) {
|
||||
this.stream.on('error', function() {});
|
||||
}
|
||||
|
||||
prefix = '[' + level + '] ';
|
||||
prefix = '[' + name + '] ';
|
||||
msg = prefix + util.format(args, false);
|
||||
msg = '(' + util.date() + '): ' + msg + '\n';
|
||||
|
||||
@ -322,9 +380,9 @@ Logger.prototype._error = function error(err) {
|
||||
|
||||
msg = (err.message + '').replace(/^ *Error: */, '');
|
||||
|
||||
this.log('error', [msg]);
|
||||
this.log(Logger.levels.ERROR, [msg]);
|
||||
|
||||
if (this.level >= Logger.levels.debug) {
|
||||
if (this.level >= Logger.levels.DEBUG) {
|
||||
if (this.stream)
|
||||
this.stream.write(err.stack + '\n');
|
||||
}
|
||||
|
||||
@ -53,6 +53,8 @@ function Node(options) {
|
||||
// Local client for walletdb
|
||||
this.client = new NodeClient(this);
|
||||
|
||||
this.startTime = -1;
|
||||
|
||||
this._bound = [];
|
||||
|
||||
this.__init();
|
||||
@ -79,7 +81,13 @@ Node.prototype.__init = function __init() {
|
||||
this.on('preopen', function() {
|
||||
self._onOpen();
|
||||
});
|
||||
|
||||
this.on('open', function() {
|
||||
self.startTime = util.now();
|
||||
});
|
||||
|
||||
this.on('close', function() {
|
||||
self.startTime = -1;
|
||||
self._onClose();
|
||||
});
|
||||
};
|
||||
@ -241,6 +249,18 @@ Node.prototype.location = function location(name) {
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get node uptime in seconds.
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
Node.prototype.uptime = function uptime() {
|
||||
if (this.startTime === -1)
|
||||
return 0;
|
||||
|
||||
return util.now() - this.startTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* Open and ensure primary wallet.
|
||||
* @returns {Promise}
|
||||
|
||||
@ -24,7 +24,6 @@ function NodeClient(node) {
|
||||
|
||||
this.node = node;
|
||||
this.network = node.network;
|
||||
this.onError = node._error.bind(node);
|
||||
this.filter = null;
|
||||
this.listen = false;
|
||||
|
||||
@ -124,7 +123,7 @@ NodeClient.prototype.getEntry = co(function* getEntry(hash) {
|
||||
*/
|
||||
|
||||
NodeClient.prototype.send = function send(tx) {
|
||||
this.node.sendTX(tx).catch(this.onError);
|
||||
this.node.sendTX(tx).catch(util.nop);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
|
||||
@ -50,11 +50,11 @@ function SPVNode(options) {
|
||||
logger: this.logger,
|
||||
db: this.options.db,
|
||||
location: this.location('spvchain'),
|
||||
maxFiles: this.options.maxFiles,
|
||||
cacheSize: this.options.cacheSize,
|
||||
witness: this.options.witness,
|
||||
forceWitness: this.options.forceWitness,
|
||||
useCheckpoints: this.options.useCheckpoints,
|
||||
maxFiles: this.options.maxFiles,
|
||||
cacheSize: this.options.cacheSize,
|
||||
spv: true
|
||||
});
|
||||
|
||||
@ -74,8 +74,7 @@ function SPVNode(options) {
|
||||
noDiscovery: this.options.noDiscovery,
|
||||
headers: this.options.headers,
|
||||
selfish: true,
|
||||
listen: false,
|
||||
spv: true
|
||||
listen: false
|
||||
});
|
||||
|
||||
this.walletdb = new WalletDB({
|
||||
@ -84,12 +83,12 @@ function SPVNode(options) {
|
||||
client: this.client,
|
||||
db: this.options.db,
|
||||
location: this.location('walletdb'),
|
||||
witness: false,
|
||||
maxFiles: this.options.walletMaxFiles,
|
||||
cacheSize: this.options.walletCacheSize,
|
||||
witness: false,
|
||||
useCheckpoints: this.options.useCheckpoints,
|
||||
startHeight: this.options.startHeight,
|
||||
wipeNoReally: this.options.wipeNoReally,
|
||||
resolution: true,
|
||||
verify: true,
|
||||
spv: true
|
||||
});
|
||||
@ -101,8 +100,8 @@ function SPVNode(options) {
|
||||
node: this,
|
||||
key: this.options.sslKey,
|
||||
cert: this.options.sslCert,
|
||||
port: this.options.httpPort || this.network.rpcPort,
|
||||
host: this.options.httpHost || '0.0.0.0',
|
||||
port: this.options.httpPort,
|
||||
host: this.options.httpHost,
|
||||
apiKey: this.options.apiKey,
|
||||
serviceKey: this.options.serviceKey,
|
||||
walletAuth: this.options.walletAuth,
|
||||
|
||||
@ -21,18 +21,19 @@ function LRU(capacity, getSize) {
|
||||
if (!(this instanceof LRU))
|
||||
return new LRU(capacity, getSize);
|
||||
|
||||
assert(typeof capacity === 'number', 'Max size must be a number.');
|
||||
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
|
||||
|
||||
this.capacity = capacity;
|
||||
this.getSize = getSize;
|
||||
|
||||
this.map = Object.create(null);
|
||||
this.size = 0;
|
||||
this.items = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.pending = null;
|
||||
|
||||
assert(typeof capacity === 'number', 'Capacity must be a number.');
|
||||
assert(capacity >= 0, 'Capacity cannot be negative.');
|
||||
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
|
||||
|
||||
this.capacity = capacity;
|
||||
this.getSize = getSize;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,6 +117,9 @@ LRU.prototype.reset = function reset() {
|
||||
LRU.prototype.set = function set(key, value) {
|
||||
var item;
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
key = key + '';
|
||||
|
||||
item = this.map[key];
|
||||
@ -151,6 +155,9 @@ LRU.prototype.set = function set(key, value) {
|
||||
LRU.prototype.get = function get(key) {
|
||||
var item;
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
key = key + '';
|
||||
|
||||
item = this.map[key];
|
||||
@ -171,6 +178,8 @@ LRU.prototype.get = function get(key) {
|
||||
*/
|
||||
|
||||
LRU.prototype.has = function get(key) {
|
||||
if (this.capacity === 0)
|
||||
return false;
|
||||
return this.map[key] != null;
|
||||
};
|
||||
|
||||
@ -183,6 +192,9 @@ LRU.prototype.has = function get(key) {
|
||||
LRU.prototype.remove = function remove(key) {
|
||||
var item;
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
key = key + '';
|
||||
|
||||
item = this.map[key];
|
||||
@ -387,6 +399,10 @@ LRU.prototype.commit = function commit() {
|
||||
|
||||
LRU.prototype.push = function push(key, value) {
|
||||
assert(this.pending);
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
this.pending.set(key, value);
|
||||
};
|
||||
|
||||
@ -397,6 +413,10 @@ LRU.prototype.push = function push(key, value) {
|
||||
|
||||
LRU.prototype.unpush = function unpush(key) {
|
||||
assert(this.pending);
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
this.pending.remove(key);
|
||||
};
|
||||
|
||||
@ -418,6 +438,7 @@ function LRUItem(key, value) {
|
||||
/**
|
||||
* LRU Batch
|
||||
* @constructor
|
||||
* @param {LRU} lru
|
||||
*/
|
||||
|
||||
function LRUBatch(lru) {
|
||||
@ -425,18 +446,37 @@ function LRUBatch(lru) {
|
||||
this.ops = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item onto the batch.
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.set = function set(key, value) {
|
||||
this.ops.push(new LRUOp(false, key, value));
|
||||
};
|
||||
|
||||
/**
|
||||
* Push a removal onto the batch.
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.remove = function remove(key) {
|
||||
this.ops.push(new LRUOp(true, key));
|
||||
this.ops.push(new LRUOp(true, key, null));
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the batch.
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.clear = function clear() {
|
||||
this.ops.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the batch.
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.commit = function commit() {
|
||||
var i, op;
|
||||
|
||||
@ -455,6 +495,10 @@ LRUBatch.prototype.commit = function commit() {
|
||||
/**
|
||||
* LRU Op
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {Boolean} remove
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
function LRUOp(remove, key, value) {
|
||||
@ -463,40 +507,8 @@ function LRUOp(remove, key, value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A null cache. Every method is a NOP.
|
||||
* @constructor
|
||||
* @param {Number} size
|
||||
*/
|
||||
|
||||
function NullCache(size) {
|
||||
this.capacity = 0;
|
||||
this.size = 0;
|
||||
this.items = 0;
|
||||
}
|
||||
|
||||
NullCache.prototype.set = function set(key, value) {};
|
||||
NullCache.prototype.remove = function remove(key) {};
|
||||
NullCache.prototype.get = function get(key) {};
|
||||
NullCache.prototype.has = function has(key) { return false; };
|
||||
NullCache.prototype.reset = function reset() {};
|
||||
NullCache.prototype.keys = function keys(key) { return []; };
|
||||
NullCache.prototype.values = function values(key) { return []; };
|
||||
NullCache.prototype.toArray = function toArray(key) { return []; };
|
||||
NullCache.prototype.batch = function batch() { return new LRUBatch(this); };
|
||||
NullCache.prototype.start = function start() {};
|
||||
NullCache.prototype.clear = function clear() {};
|
||||
NullCache.prototype.drop = function drop() {};
|
||||
NullCache.prototype.commit = function commit() {};
|
||||
NullCache.prototype.push = function push(key, value) {};
|
||||
NullCache.prototype.unpush = function unpush(key) {};
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
exports = LRU;
|
||||
exports.LRU = LRU;
|
||||
exports.Nil = NullCache;
|
||||
|
||||
module.exports = exports;
|
||||
module.exports = LRU;
|
||||
|
||||
@ -125,17 +125,6 @@ util.isBase58 = function isBase58(obj) {
|
||||
return typeof obj === 'string' && /^[1-9a-zA-Z]+$/.test(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return uptime (shim for browser).
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
util.uptime = function uptime() {
|
||||
if (!process.uptime)
|
||||
return 0;
|
||||
return process.uptime();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return hrtime (shim for browser).
|
||||
* @param {Array} time
|
||||
|
||||
@ -54,27 +54,21 @@ function WalletDB(options) {
|
||||
if (!(this instanceof WalletDB))
|
||||
return new WalletDB(options);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
AsyncObject.call(this);
|
||||
|
||||
this.options = options;
|
||||
this.network = Network.get(options.network);
|
||||
this.logger = options.logger || Logger.global;
|
||||
this.spv = options.spv || false;
|
||||
this.client = options.client;
|
||||
this.onError = this._onError.bind(this);
|
||||
this.options = new WalletOptions(options);
|
||||
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger;
|
||||
this.client = this.options.client;
|
||||
this.db = LDB(this.options);
|
||||
|
||||
this.state = new ChainState();
|
||||
this.wallets = Object.create(null);
|
||||
this.depth = 0;
|
||||
this.wallets = {};
|
||||
this.keepBlocks = this.network.block.keepBlocks;
|
||||
this.rescanning = false;
|
||||
this.bound = false;
|
||||
|
||||
// We need one read lock for `get` and `create`.
|
||||
// It will hold locks specific to wallet ids.
|
||||
this.readLock = new Lock.Mapped();
|
||||
this.writeLock = new Lock();
|
||||
this.txLock = new Lock();
|
||||
@ -82,22 +76,7 @@ function WalletDB(options) {
|
||||
this.widCache = new LRU(10000);
|
||||
this.pathMapCache = new LRU(100000);
|
||||
|
||||
// Try to optimize for up to 1m addresses.
|
||||
// We use a regular bloom filter here
|
||||
// because we never want members to
|
||||
// lose membership, even if quality
|
||||
// degrades.
|
||||
// Memory used: 1.7mb
|
||||
this.filter = Bloom.fromRate(1000000, 0.001, this.spv ? 1 : -1);
|
||||
|
||||
this.db = LDB({
|
||||
location: this.options.location,
|
||||
db: this.options.db,
|
||||
maxFiles: this.options.maxFiles,
|
||||
cacheSize: this.options.cacheSize,
|
||||
compression: true,
|
||||
bufferKeys: !util.isBrowser
|
||||
});
|
||||
this.filter = Bloom.fromRate(1000000, 0.001, this.options.spv ? 1 : -1);
|
||||
}
|
||||
|
||||
util.inherits(WalletDB, AsyncObject);
|
||||
@ -154,16 +133,6 @@ WalletDB.prototype._close = co(function* close() {
|
||||
yield this.db.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Emit an error.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype._onError = function onError(err) {
|
||||
this.emit('error', err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the walletdb.
|
||||
* @returns {Promise}
|
||||
@ -202,25 +171,41 @@ WalletDB.prototype.bind = function bind() {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.client.on('block connect', function(entry, txs) {
|
||||
self.addBlock(entry, txs).catch(self.onError);
|
||||
});
|
||||
this.client.on('block connect', co(function* (entry, txs) {
|
||||
try {
|
||||
yield self.addBlock(entry, txs);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
}
|
||||
}));
|
||||
|
||||
this.client.on('block disconnect', function(entry) {
|
||||
self.removeBlock(entry).catch(self.onError);
|
||||
});
|
||||
this.client.on('block disconnect', co(function* (entry) {
|
||||
try {
|
||||
yield self.removeBlock(entry);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
}
|
||||
}));
|
||||
|
||||
this.client.on('block rescan', cob(function* (entry, txs) {
|
||||
yield self.rescanBlock(entry, txs);
|
||||
}));
|
||||
|
||||
this.client.on('tx', function(tx) {
|
||||
self.addTX(tx).catch(self.onError);
|
||||
});
|
||||
this.client.on('tx', co(function* (tx) {
|
||||
try {
|
||||
yield self.addTX(tx);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
}
|
||||
}));
|
||||
|
||||
this.client.on('chain reset', function(tip) {
|
||||
self.resetChain(tip).catch(self.onError);
|
||||
});
|
||||
this.client.on('chain reset', co(function* (tip) {
|
||||
try {
|
||||
yield self.resetChain(tip);
|
||||
} catch (e) {
|
||||
self.emit('error', e);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1621,8 +1606,8 @@ WalletDB.prototype.syncState = co(function* syncState(tip) {
|
||||
height = state.height;
|
||||
blocks = height - tip.height;
|
||||
|
||||
if (blocks > this.keepBlocks)
|
||||
blocks = this.keepBlocks;
|
||||
if (blocks > this.options.keepBlocks)
|
||||
blocks = this.options.keepBlocks;
|
||||
|
||||
for (i = 0; i < blocks; i++) {
|
||||
batch.del(layout.h(height));
|
||||
@ -1632,7 +1617,7 @@ WalletDB.prototype.syncState = co(function* syncState(tip) {
|
||||
// Prune old hashes.
|
||||
assert(tip.height === state.height + 1, 'Bad chain sync.');
|
||||
|
||||
height = tip.height - this.keepBlocks;
|
||||
height = tip.height - this.options.keepBlocks;
|
||||
|
||||
if (height >= 0)
|
||||
batch.del(layout.h(height));
|
||||
@ -2153,6 +2138,125 @@ WalletDB.prototype._resetChain = co(function* resetChain(entry) {
|
||||
yield this.scan();
|
||||
});
|
||||
|
||||
/**
|
||||
* WalletOptions
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function WalletOptions(options) {
|
||||
if (!(this instanceof WalletOptions))
|
||||
return new WalletOptions(options);
|
||||
|
||||
this.network = Network.primary;
|
||||
this.logger = Logger.global;
|
||||
this.client = null;
|
||||
|
||||
this.location = null;
|
||||
this.db = 'memory';
|
||||
this.maxFiles = 64;
|
||||
this.cacheSize = 16 << 20;
|
||||
this.compression = true;
|
||||
this.bufferKeys = !util.isBrowser;
|
||||
|
||||
this.spv = false;
|
||||
this.witness = false;
|
||||
this.useCheckpoints = false;
|
||||
this.startHeight = 0;
|
||||
this.keepBlocks = this.network.block.keepBlocks;
|
||||
this.wipeNoReally = false;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from object.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {WalletOptions}
|
||||
*/
|
||||
|
||||
WalletOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
if (options.network != null)
|
||||
this.network = Network.get(options.network);
|
||||
|
||||
this.keepBlocks = this.network.block.keepBlocks;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.client != null) {
|
||||
assert(typeof options.client === 'object');
|
||||
this.client = options.client;
|
||||
}
|
||||
|
||||
if (options.location != null) {
|
||||
assert(typeof options.location === 'string');
|
||||
this.location = options.location;
|
||||
}
|
||||
|
||||
if (options.db != null) {
|
||||
assert(typeof options.db === 'string');
|
||||
this.db = options.db;
|
||||
}
|
||||
|
||||
if (options.maxFiles != null) {
|
||||
assert(util.isNumber(options.maxFiles));
|
||||
this.maxFiles = options.maxFiles;
|
||||
}
|
||||
|
||||
if (options.cacheSize != null) {
|
||||
assert(util.isNumber(options.cacheSize));
|
||||
this.cacheSize = options.cacheSize;
|
||||
}
|
||||
|
||||
if (options.compression != null) {
|
||||
assert(typeof options.compression === 'boolean');
|
||||
this.compression = options.compression;
|
||||
}
|
||||
|
||||
if (options.spv != null) {
|
||||
assert(typeof options.spv === 'boolean');
|
||||
this.spv = options.spv;
|
||||
}
|
||||
|
||||
if (options.witness != null) {
|
||||
assert(typeof options.witness === 'boolean');
|
||||
this.witness = options.witness;
|
||||
}
|
||||
|
||||
if (options.useCheckpoints != null) {
|
||||
assert(typeof options.useCheckpoints === 'boolean');
|
||||
this.useCheckpoints = options.useCheckpoints;
|
||||
}
|
||||
|
||||
if (options.startHeight != null) {
|
||||
assert(typeof options.startHeight === 'number');
|
||||
assert(options.startHeight >= 0);
|
||||
this.startHeight = options.startHeight;
|
||||
}
|
||||
|
||||
if (options.wipeNoReally != null) {
|
||||
assert(typeof options.wipeNoReally === 'boolean');
|
||||
this.wipeNoReally = options.wipeNoReally;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate chain options from object.
|
||||
* @param {Object} options
|
||||
* @returns {WalletOptions}
|
||||
*/
|
||||
|
||||
WalletOptions.fromOptions = function fromOptions(options) {
|
||||
return new WalletOptions().fromOptions(options);
|
||||
};
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
var assert = require('assert');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var os = require('os');
|
||||
var cp = require('child_process');
|
||||
var util = require('../utils/util');
|
||||
var co = require('../utils/co');
|
||||
var global = util.global;
|
||||
@ -17,8 +19,6 @@ var jobs = require('./jobs');
|
||||
var Parser = require('./parser');
|
||||
var Framer = require('./framer');
|
||||
var packets = require('./packets');
|
||||
var os = require('os');
|
||||
var cp = require('child_process');
|
||||
|
||||
/**
|
||||
* A worker pool.
|
||||
@ -39,14 +39,13 @@ function WorkerPool(options) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.size = Math.max(1, options.size || WorkerPool.CORES);
|
||||
this.timeout = options.timeout || 60000;
|
||||
this.size = WorkerPool.CORES;
|
||||
this.timeout = 60000;
|
||||
this.children = [];
|
||||
this.nonce = 0;
|
||||
this.enabled = true;
|
||||
|
||||
this.set(options);
|
||||
}
|
||||
|
||||
util.inherits(WorkerPool, EventEmitter);
|
||||
@ -88,7 +87,13 @@ WorkerPool.cleanup = function cleanup() {
|
||||
WorkerPool.children.pop().destroy();
|
||||
};
|
||||
|
||||
WorkerPool._exitBound = false;
|
||||
/**
|
||||
* Whether exit events have been bound globally.
|
||||
* @private
|
||||
* @type {Boolean}
|
||||
*/
|
||||
|
||||
WorkerPool.bound = false;
|
||||
|
||||
/**
|
||||
* Bind to process events in
|
||||
@ -96,14 +101,14 @@ WorkerPool._exitBound = false;
|
||||
* @private
|
||||
*/
|
||||
|
||||
WorkerPool._bindExit = function _bindExit() {
|
||||
WorkerPool.bindExit = function bindExit() {
|
||||
if (util.isBrowser)
|
||||
return;
|
||||
|
||||
if (WorkerPool._exitBound)
|
||||
if (WorkerPool.bound)
|
||||
return;
|
||||
|
||||
WorkerPool._exitBound = true;
|
||||
WorkerPool.bound = true;
|
||||
|
||||
function onSignal() {
|
||||
WorkerPool.cleanup();
|
||||
@ -143,6 +148,33 @@ WorkerPool._bindExit = function _bindExit() {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set worker pool options.
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
WorkerPool.prototype.set = function set(options) {
|
||||
if (!options)
|
||||
return;
|
||||
|
||||
if (options.enabled != null) {
|
||||
assert(typeof options.enabled === 'boolean');
|
||||
this.enabled = options.enabled;
|
||||
}
|
||||
|
||||
if (options.size != null) {
|
||||
assert(util.isNumber(options.size));
|
||||
assert(options.size > 0);
|
||||
this.size = options.size;
|
||||
}
|
||||
|
||||
if (options.timeout != null) {
|
||||
assert(util.isNumber(options.timeout));
|
||||
assert(options.timeout > 0);
|
||||
this.timeout = options.timeout;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Spawn a new worker.
|
||||
* @param {Number} id - Worker ID.
|
||||
@ -541,7 +573,7 @@ Worker.prototype._bind = function _bind() {
|
||||
|
||||
WorkerPool.children.push(this);
|
||||
|
||||
WorkerPool._bindExit();
|
||||
WorkerPool.bindExit();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -828,31 +860,24 @@ function getCores() {
|
||||
if (os.unsupported)
|
||||
return 2;
|
||||
|
||||
return os.cpus().length;
|
||||
return Math.max(1, os.cpus().length);
|
||||
}
|
||||
|
||||
/*
|
||||
* Default
|
||||
* Default Pool
|
||||
*/
|
||||
|
||||
exports.pool = new WorkerPool();
|
||||
exports.pool.enabled = false;
|
||||
|
||||
exports.set = function set(options) {
|
||||
if (typeof options.useWorkers === 'boolean')
|
||||
this.pool.enabled = options.useWorkers;
|
||||
|
||||
if (util.isNumber(options.maxWorkers))
|
||||
this.pool.size = options.maxWorkers;
|
||||
|
||||
if (util.isNumber(options.workerTimeout))
|
||||
this.pool.timeout = options.workerTimeout;
|
||||
this.pool.set(options);
|
||||
};
|
||||
|
||||
exports.set({
|
||||
useWorkers: +process.env.BCOIN_USE_WORKERS === 1,
|
||||
maxWorkers: +process.env.BCOIN_MAX_WORKERS,
|
||||
workerTimeout: +process.env.BCOIN_WORKER_TIMEOUT
|
||||
maxWorkers: +process.env.BCOIN_MAX_WORKERS || null,
|
||||
workerTimeout: +process.env.BCOIN_WORKER_TIMEOUT || null
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@ -45,7 +45,7 @@ describe('HTTP', function() {
|
||||
var info = yield wallet.client.getInfo();
|
||||
assert.equal(info.network, node.network.type);
|
||||
assert.equal(info.version, USER_VERSION);
|
||||
assert.equal(info.pool.agent, node.pool.userAgent);
|
||||
assert.equal(info.pool.agent, node.pool.options.agent);
|
||||
assert.equal(typeof info.chain, 'object');
|
||||
assert.equal(info.chain.height, 0);
|
||||
}));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user