diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index fa901c31..dc73c031 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -62,10 +62,28 @@ Chain.prototype._init = function _init() { var self = this; // Hook into events for debugging - this.on('block', function(block, entry, peer) { - // var host = peer ? peer.host : 'unknown'; - // utils.debug('Block %s (%d) added to chain (%s)', - // utils.revHex(entry.hash), entry.height, host); + // this.on('block', function(block, entry, peer) { + // var host = peer ? peer.host : 'unknown'; + // utils.debug('Block %s (%d) added to chain (%s)', + // utils.revHex(entry.hash), entry.height, host); + // }); + + this.on('competitor', function(block, entry, peer) { + var host = peer ? peer.host : 'unknown'; + utils.debug('Heads up: Competing chain at height %d:' + + ' tip-height=%d competitor-height=%d' + + ' tip-hash=%s competitor-hash=%s' + + ' tip-chainwork=%s competitor-chainwork=%s' + + ' chainwork-diff=%s (%s)', + entry.height, + utils.revHex(self.tip.hash), + utils.revHex(entry.hash), + self.tip.height, + entry.height, + self.tip.chainwork.toString(), + entry.chainwork.toString(), + self.tip.chainwork.sub(entry.chainwork).toString(), + host); }); this.on('resolved', function(block, entry, peer) { @@ -863,36 +881,14 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) { }); }; -Chain.prototype._addEntry = function _addEntry(entry, block, callback) { - // Set main chain only if chainwork is higher. - // Add the block but do _not_ connect the inputs. - if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { - return this.db.save(entry, block, false, function(err) { - if (err) - return callback(err); - - return callback(null, false); - }); - } - - // Attempt to add block to the chain index. - return this._setBestChain(entry, block, function(err) { - if (err) - return callback(err); - - return callback(null, true); - }); -}; - Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) { var self = this; - this.lastUpdate = utils.now(); - function done(err) { if (err) return callback(err); + // Save block and connect inputs. self.db.save(entry, block, true, function(err) { if (err) return callback(err); @@ -905,20 +901,45 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) { self.emit('tip', entry); - return callback(); + // Return true (added to the main chain) + return callback(null, true); }); } + // Update the timestamp to + // maintain a time delta of blocks. + this.lastUpdate = utils.now(); + + // We don't have a genesis block yet. if (!this.tip) { if (entry.hash !== network.genesis.hash) return utils.asyncify(callback)(new Error('Bad genesis block.')); - done(); - } else if (entry.prevBlock === this.tip.hash) { - done(); - } else { - self._reorganize(entry, block, done); + return done(); } + + // The block is on a side chain if the + // chainwork is less than or equal to + // our tip's. Add the block but do _not_ + // connect the inputs. + if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { + return this.db.save(entry, block, false, function(err) { + if (err) + return callback(err); + + // Return false (added to side chain) + return callback(null, false); + }); + } + + // Everything is in order. + if (entry.prevBlock === this.tip.hash) + return done(); + + // A higher fork has arrived. + // Time to reorganize the chain. + utils.debug('WARNING: Reorganizing chain.'); + return this._reorganize(entry, block, done); }; Chain.prototype.reset = function reset(height, callback, force) { @@ -1214,7 +1235,7 @@ Chain.prototype.add = function add(initial, peer, callback, force) { }, prev); // Attempt to add block to the chain index. - self._addEntry(entry, block, function(err, mainChain) { + self._setBestChain(entry, block, function(err, mainChain) { if (err) return done(err); @@ -1226,6 +1247,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) { // orphan) only if it is on the main chain. if (mainChain) self.emit('block', block, entry, peer); + else + self.emit('competitor', block, entry, peer); if (block.hash('hex') !== initial.hash('hex')) self.emit('resolved', block, entry, peer); diff --git a/lib/bcoin/chainblock.js b/lib/bcoin/chainblock.js index bf398581..bb532a3c 100644 --- a/lib/bcoin/chainblock.js +++ b/lib/bcoin/chainblock.js @@ -242,6 +242,10 @@ ChainBlock.prototype.getMedianTimeAsync = function getMedianTimeAsync(callback) }); }; +ChainBlock.prototype.__defineGetter__('rhash', function() { + return utils.revHex(this.hash); +}); + ChainBlock.prototype.toRaw = function toRaw() { var p = new BufferWriter(); diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index e6174e2d..85c2149f 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -353,7 +353,6 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) { batch.put('c/b/' + entry.hash, height); batch.put('c/c/' + entry.hash, entry.toRaw()); - batch.put('c/n/' + entry.prevBlock, hash); this.cacheHash.set(entry.hash, entry); @@ -367,6 +366,7 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) { this.cacheHeight.set(entry.height, entry); + batch.put('c/n/' + entry.prevBlock, hash); batch.put('c/h/' + pad32(entry.height), hash); batch.put('c/t', hash); @@ -399,7 +399,7 @@ ChainDB.prototype.getTip = function getTip(callback) { ChainDB.prototype.connect = function connect(block, callback) { var self = this; - var batch; + var batch, hash; this._ensureEntry(block, function(err, entry) { if (err) @@ -409,9 +409,11 @@ ChainDB.prototype.connect = function connect(block, callback) { return callback(); batch = self.db.batch(); + hash = new Buffer(entry.hash, 'hex'); - batch.put('c/h/' + pad32(entry.height), new Buffer(entry.hash, 'hex')); - batch.put('c/t', new Buffer(entry.hash, 'hex')); + batch.put('c/n/' + entry.prevBlock, hash); + batch.put('c/h/' + pad32(entry.height), hash); + batch.put('c/t', hash); self.cacheHeight.set(entry.height, entry); @@ -443,6 +445,7 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) { batch = self.db.batch(); + batch.del('c/n/' + entry.prevBlock); batch.del('c/h/' + pad32(entry.height)); batch.put('c/t', new Buffer(entry.prevBlock, 'hex'));