refactor: options.

This commit is contained in:
Christopher Jeffrey 2017-01-14 14:41:55 -08:00
parent 9dbfaf3127
commit a486bd3a18
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
24 changed files with 1640 additions and 842 deletions

View File

@ -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

View File

@ -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);
};
/**

View File

@ -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

View File

@ -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');

View File

@ -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

View File

@ -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
*/

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
};
/**

View File

@ -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;
};

View File

@ -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,

View File

@ -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');
}

View File

@ -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}

View File

@ -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();
};

View File

@ -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,

View File

@ -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;

View File

@ -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

View File

@ -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
*/

View File

@ -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
});
/*

View File

@ -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);
}));