diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 5c520f5c..d4ac891c 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -187,11 +187,14 @@ Chain.prototype.__defineGetter__('height', function() { Chain.prototype._lock = function _lock(func, args, force) { var self = this; - var block; + var block, called; if (force) { assert(this.busy); - return function() {}; + return function unlock() { + assert(!called); + called = true; + }; } if (this.busy) { @@ -214,6 +217,9 @@ Chain.prototype._lock = function _lock(func, args, force) { return function unlock() { var item, block; + assert(!called); + called = true; + self.busy = false; if (func === Chain.prototype.add) { @@ -584,9 +590,6 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba // BIP30 - Ensure there are no duplicate txids self.blockdb.hasTX(hash, function(err, result) { - if (called) - return; - if (err) return done(err); @@ -759,8 +762,6 @@ Chain.prototype.resetHeight = function resetHeight(height, force) { // no longer need. this.purgeOrphans(); this.purgePending(); - - unlock(); }; Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback, force) { @@ -1262,6 +1263,9 @@ Chain.prototype.add = function add(initial, peer, callback, force) { if (self.orphan.size > self.orphanLimit) self.pruneOrphans(); + // Keep track of total blocks handled. + self.total += total; + // We intentionally did not asyncify the // callback so if it calls chain.add, it // still gets added to the queue. The @@ -1269,15 +1273,11 @@ Chain.prototype.add = function add(initial, peer, callback, force) { // so we don't cause a stack overflow if // these end up being all sync chain.adds. utils.nextTick(function() { - // XXX Possibly put `unlock()` above callback! + unlock(); if (err) callback(err); else callback(null, total); - - self.total += total; - - unlock(); }); } }; diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index 8331ce03..1d573ce6 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -123,8 +123,7 @@ ChainDB.prototype.load = function load(start, callback) { utils.debug( 'Blockchain is corrupt after height %d. Resetting.', height); - self.resetHeightAsync(height, callback); - return; + return self.resetHeightAsync(height, callback); } utils.debug('Chain successfully loaded.'); callback(); @@ -157,6 +156,7 @@ ChainDB.prototype.closeSync = function closeSync() { this.ramdisk = null; return; } + fs.close(this.fd); this.fd = null; }; @@ -174,6 +174,7 @@ ChainDB.prototype.closeAsync = function closeAsync(callback) { fs.close(this.fd, function(err) { if (err) return callback(err); + self.fd = null; return callback(); }); @@ -349,7 +350,7 @@ ChainDB.prototype.saveSync = function saveSync(entry) { if (entry.height * BLOCK_SIZE !== this.size) { utils.debug('Warning attempt to write to height: %d/%d', this.height); - return; + return false; } // Cache the past 1001 blocks in memory @@ -402,8 +403,10 @@ ChainDB.prototype.saveAsync = function saveAsync(entry, callback) { return callback(err); assert(self.queue[entry.height]); + delete self.queue[entry.height]; self.queueSize--; + if (self.queueSize === 0) self.emit('flush'); @@ -411,44 +414,39 @@ ChainDB.prototype.saveAsync = function saveAsync(entry, callback) { }); }; -ChainDB.prototype._drop = function _drop(height) { - assert(height >= 0); - - // Potential race condition here. Not sure how - // to handle this. - if (this.queue[height]) { - utils.debug('Warning: write job in progress.'); - // delete this.queue[height]; - } - - delete this.cache[height]; -}; - ChainDB.prototype.resetHeightSync = function resetHeightSync(height) { var self = this; - var size, count, existing; var osize = this.size; var ohighest = this.highest; var otip = this.tip; + var size, count, existing; if (typeof height === 'string') height = this.heightLookup[height]; assert(height >= 0); + assert(this.tip); size = (height + 1) * BLOCK_SIZE; count = this.getSize(); + if (height > count - 1) + throw new Error('Height too high.'); + if (height === count - 1) return; - assert(height <= count - 1); - assert(this.tip); - for (i = height + 1; i < count; i++) { existing = this.getSync(i); + assert(existing); - this._drop(i); + + // Warn of potential race condition + // (handled with _onFlush). + if (this.queue[i]) + utils.debug('Warning: write job in progress.'); + + delete this.cache[i]; delete this.heightLookup[existing.hash]; } @@ -481,28 +479,29 @@ ChainDB.prototype.resetHeightSync = function resetHeightSync(height) { ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback) { var self = this; - var size, count, pending, called; var osize = this.size; var ohighest = this.highest; var otip = this.tip; + var size, count, pending, called; + + callback = utils.asyncify(callback); if (typeof height === 'string') height = this.heightLookup[height]; assert(height >= 0); - - callback = utils.asyncify(callback); + assert(this.tip); size = (height + 1) * BLOCK_SIZE; count = this.getSize(); pending = count - (height + 1); + if (height > count - 1) + return callback(new Error('Height too high')); + if (height === count - 1) return callback(); - assert(height <= count - 1); - assert(this.tip); - // Prevent any more writes // by setting this early. this.size = size; @@ -527,8 +526,14 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback) assert(existing); - self._drop(i); + // Warn of potential race condition + // (handled with _onFlush). + if (self.queue[i]) + utils.debug('Warning: write job in progress.'); + + delete self.cache[i]; delete self.heightLookup[existing.hash]; + if (!--pending) done(); }, true); diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 9dd2ebfa..c873128f 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -19,7 +19,7 @@ var fs = bcoin.fs; * Mempool */ -function Mempool(pool, options) { +function Mempool(node, options) { if (!(this instanceof Mempool)) return new Mempool(pool, options); @@ -27,8 +27,8 @@ function Mempool(pool, options) { options = {}; this.options = options; - this.pool = pool; - this.blockdb = pool.blockdb; + this.node = node; + this.blockdb = node.blockdb; this.txs = {}; this.spent = {}; @@ -235,7 +235,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { data: tx.hash(), reason: 'non-standard' }); - pool.setMisbehavior(peer, 100); + self.node.pool.setMisbehavior(peer, 100); return callback(new Error('TX is not standard.')); } @@ -245,7 +245,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { data: tx.hash(), reason: 'non-standard-inputs' }); - pool.setMisbehavior(peer, 100); + self.node.pool.setMisbehavior(peer, 100); return callback(new Error('TX inputs are not standard.')); } @@ -255,11 +255,11 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { data: tx.hash(), reason: 'nonexistent-coins' }); - pool.setMisbehavior(peer, 100); + self.node.pool.setMisbehavior(peer, 100); return callback(new Error('TX is spending coins that it does not have.')); } - height = self.pool.chain.height + 1; + height = self.node.pool.chain.height + 1; ts = utils.now(); if (!tx.isFinal(height, ts)) { return callback(new Error('TX is not final.')); @@ -267,7 +267,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { data: tx.hash(), reason: 'not-final' }); - pool.setMisbehavior(peer, 100); + self.node.pool.setMisbehavior(peer, 100); return callback(new Error('TX is not final.')); } @@ -299,7 +299,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { data: tx.hash(), reason: 'negative-value' }); - pool.setMisbehavior(peer, 100); + self.node.pool.setMisbehavior(peer, 100); return callback(new Error('TX is spending negative coins.')); } } @@ -310,7 +310,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { data: tx.hash(), reason: 'script-failed' }); - pool.setMisbehavior(peer, 100); + self.node.pool.setMisbehavior(peer, 100); return callback(new Error('TX did not verify.')); } diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index d9b190d5..905fb1a3 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -17,7 +17,7 @@ var EventEmitter = require('events').EventEmitter; * Miner */ -function Miner(options) { +function Miner(pool, options) { if (!(this instanceof Miner)) return new Miner(options); @@ -30,15 +30,17 @@ function Miner(options) { this.address = this.options.address; this.msg = this.options.msg || 'mined by bcoin'; - this.pool = options.pool || bcoin.pool.global; - this.chain = options.chain || this.pool.chain || bcoin.chain.global; + this.pool = pool || bcoin.pool.global; + this.chain = this.pool.chain; + this.mempool = this.pool.mempool; + this.blockdb = this.pool.blockdb; this.running = false; this.timeout = null; this.interval = null; this.fee = new bn(0); - this.last = this.chain.getTip(); + this.last = this.chain.tip; this.block = null; this.iterations = 0; this._begin = utils.now(); @@ -51,18 +53,26 @@ inherits(Miner, EventEmitter); Miner.prototype._init = function _init() { var self = this; - this.pool.on('tx', function(tx) { - self.addTX(tx); - }); + if (this.mempool) { + this.mempool.on('tx', function(tx) { + if (!self.running) + return; + self.addTX(tx); + }); + } else { + this.pool.on('tx', function(tx) { + if (!self.running) + return; + self.addTX(tx); + }); + } - this.chain.on('block', function(block) { + this.chain.on('add block', function(block) { + if (!self.running) + return; self.addBlock(block); }); - // this.chain.on('tip', function(entry) { - // self.addBlock(entry); - // }); - this.on('block', function(block) { utils.debug( 'Found block: %d (%s)', @@ -123,7 +133,10 @@ Miner.prototype.add = function add(msg) { Miner.prototype.addBlock = function addBlock(block) { if (!block) - block = this.chain.getTip(); + block = this.chain.tip; + + if (!this.last) + this.last = this.chain.tip; // Somebody found the next block before // us, start over with the new target. @@ -222,7 +235,7 @@ Miner.prototype.createBlock = function createBlock(tx) { seq: 0xffffffff }); - if (script.getSize(coinbase.inputs[0].script) > 100) + if (bcoin.script.getSize(coinbase.inputs[0].script) > 100) throw new Error('Coinbase script is too large'); coinbase.addOutput({ diff --git a/lib/bcoin/node.js b/lib/bcoin/node.js index 3b7fe3bb..1db5b47c 100644 --- a/lib/bcoin/node.js +++ b/lib/bcoin/node.js @@ -39,6 +39,7 @@ function Node(options) { this.mempool = null; this.pool = null; this.chain = null; + this.miner = null; Node.global = this; @@ -50,18 +51,19 @@ inherits(Node, EventEmitter); Node.prototype._init = function _init() { var self = this; - this.blockdb = new bcoin.blockdb(this.options.blockdb); - this.mempool = new bcoin.mempool(this, this.options.mempool); - if (!this.options.pool) this.options.pool = {}; + this.blockdb = new bcoin.blockdb(this.options.blockdb); + this.mempool = new bcoin.mempool(this, this.options.mempool); + this.options.pool.spv = false; this.options.pool.blockdb = this.blockdb; this.options.pool.mempool = this.mempool; this.pool = new bcoin.pool(this.options.pool); this.chain = this.pool.chain; + this.miner = new bcoin.miner(this.pool, this.options.miner); this.mempool.on('error', function(err) { self.emit('error', err);