From dd54dcad11855869ae2b14c5c506073f2a475833 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Mar 2016 04:21:19 -0800 Subject: [PATCH] fixes. better reorg. --- lib/bcoin/blockdb.js | 44 ++++++- lib/bcoin/chain.js | 196 +++++++++++++++++++------------- lib/bcoin/chainblock.js | 9 +- lib/bcoin/chaindb.js | 47 +++++++- lib/bcoin/pool.js | 4 +- lib/bcoin/protocol/constants.js | 2 +- 6 files changed, 206 insertions(+), 96 deletions(-) diff --git a/lib/bcoin/blockdb.js b/lib/bcoin/blockdb.js index e584f948..ffa0ce1c 100644 --- a/lib/bcoin/blockdb.js +++ b/lib/bcoin/blockdb.js @@ -1023,11 +1023,53 @@ BlockDB.prototype.resetHeight = function resetHeight(height, callback, emit) { }); }; +BlockDB.prototype.getTip = function getTip(callback) { + var tip; + + assert(this.node); + assert(this.node.chain); + assert(this.node.chain.tip); + + tip = this.node.chain.tip.hash; + + return callback(null, tip); +}; + +BlockDB.prototype.resetHash = function resetHash(hash, callback, emit) { + var self = this; + this.getTip(function(err, tip) { + if (err) + return callback(err); + + if (!tip) + return callback(new Error('Cannot reset to hash ' + hash)); + + (function next(tip) { + if (tip === hash) + return callback(); + + self.removeBlock(tip, function(err, block) { + if (err) + return callback(err); + + if (!block) + return callback(new Error('Cannot reset all blocks.')); + + // Emit the blocks we removed. + if (emit) + emit(block); + + next(block.prevBlock); + }); + })(hash); + }); +}; + BlockDB.prototype._getEntry = function _getEntry(height, callback) { if (!this.node) return callback(); - return this.node.chain.db.getAsync(height, callback); + return this.node.chain.db.get(height, callback); }; /** diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 816b3e78..14bae0d8 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -334,17 +334,20 @@ Chain.prototype._preload = function _preload(callback) { return; blocks.forEach(function(data) { - var entry = bcoin.chainblock.fromRaw(self, height, data); - var block = bcoin.headers(entry); - var start; + var entry = bcoin.chainblock.fromRaw(self, data); + var block, start; + + entry.height = height; + + block = bcoin.headers(entry); // Do some paranoid checks. if (lastEntry && entry.prevBlock !== lastEntry.hash) { start = Math.max(0, height - 2); stream.destroy(); - return self.resetHeight(start, function(e) { - if (e) - throw e; + return self.resetHeight(start, function(err) { + if (err) + throw err; return callback(new Error('Corrupt headers.'), start + 1); }); } @@ -354,17 +357,18 @@ Chain.prototype._preload = function _preload(callback) { if (!block.verifyHeaders()) { start = Math.max(0, height - 2); stream.destroy(); - return self.resetHeight(start, function(e) { - if (e) - throw e; + return self.resetHeight(start, function(err) { + if (err) + throw err; return callback(new Error('Bad headers.'), start + 1); }); } - lastEntry = entry; - + // Calculate chainwork. delete entry.chainwork; - entry.chainwork = entry.getChainwork(); + entry.chainwork = entry.getChainwork(lastEntry); + + lastEntry = entry; // Make sure the genesis block is correct. if (height === 0 && entry.hash !== network.genesis.hash) { @@ -375,8 +379,8 @@ Chain.prototype._preload = function _preload(callback) { // Filthy hack to avoid writing // redundant blocks to disk! if (height <= chainHeight) { - self.db._cache(entry); - self.db._populate(entry); + self.db.addCache(entry); + self.db.bloom(entry.hash, 'hex'); } else { self.db.save(entry); } @@ -616,6 +620,7 @@ Chain.prototype._verify2 = function _verify(block, prev, callback) { } height = prev.height + 1; + prev.getMedianTime(function(err, medianTime) { if (err) return callback(err); @@ -1045,46 +1050,110 @@ Chain.prototype._findFork = function _findFork(fork, longer, callback) { })(); }; -Chain.prototype._reorganize = function _reorganize(entry, block, callback) { +Chain.prototype._reorganize = function _reorganize(entry, callback) { var self = this; - function done(err) { + // Find the fork and connect/disconnect blocks. + // NOTE: Bitcoind disconnects and reconnects the + // forked block for some reason. We don't do this + // since it was already emitted for the wallet + // and mempool to handle. Technically bitcoind + // shouldn't have done it either. + return this._findFork(this.tip, entry, function(err, fork) { if (err) return callback(err); - self.emit('fork', block, { - height: entry.height, - expected: self.tip.hash, - received: entry.hash, - checkpoint: false - }); - - return callback(); - } - - return this._findFork(this.tip, entry, function(err, fork) { - if (err) - return done(err); - assert(fork); - self.db.resetHeight(fork.height, function(err) { - if (err) - return done(err); - - if (!self.blockdb) - return done(); - - self.blockdb.resetHeight(fork.height, function(err) { + // Disconnect blocks/txs. + function disconnect(callback) { + self.db.resetHash(fork.hash, function(err) { if (err) - return done(err); + return callback(err); - return done(); - }, function(block) { - self.emit('remove block', block); + if (!self.blockdb) + return callback(); + + self.blockdb.resetHash(fork.hash, function(err) { + if (err) + return callback(err); + + return callback(); + }, function(block) { + self.emit('remove block', block); + }); + }, function(entry) { + self.emit('remove entry', entry); + }); + } + + // Connect blocks/txs. + function connect(callback) { + var entries = []; + + (function collect(entry) { + if (entry.hash === fork.hash) + return finish(); + + self.db.get(entry.prevBlock, function(err, entry) { + if (err) + return callback(err); + + assert(entry); + + entries.push(entry); + collect(entry); + }); + })(entry); + + function finish() { + entries = entries.slice().reverse(); + assert(entries.length > 0); + + entries.forEach(function(entry) { + self.emit('add entry', entry); + }); + + if (!self.blockdb) + return callback(); + + utils.forEachSerial(entries, function(err, entry) { + return self.blockdb.getBlock(entry.hash, function(err, block) { + if (err) + return callback(err); + + assert(block); + + self.emit('add block', block); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + return callback(); + }); + } + } + + return disconnect(function(err) { + if (err) + return callback(err); + + return connect(function(err) { + if (err) + return callback(err); + + self.emit('fork', block, { + height: fork.height, + expected: self.tip.hash, + received: fork.hash, + checkpoint: false + }); + + return callback(); }); - }, function(entry) { - self.emit('remove entry', entry); }); }); }; @@ -1104,7 +1173,7 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) { } else if (entry.prevBlock === this.tip.hash) { done(); } else { - self._reorganize(entry, block, done); + self._reorganize(entry, done); } function done(err) { @@ -1218,34 +1287,6 @@ Chain.prototype.revertHeight = function revertHeight(height, callback, force) { }); }; -Chain.prototype._revertLast = function _revertLast(existing, callback, force) { - var self = this; - - var unlock = this._lock(_revertLast, [existing, callback], force); - if (!unlock) - return; - - function done(err, result) { - unlock(); - callback(err, result); - } - - this.resetHeight(existing.height - 1, function(err) { - if (err) - return done(err); - - self._removeBlock(existing.hash, function(err, existingBlock) { - if (err) - return done(err); - - if (existingBlock) - self.emit('remove block', existingBlock); - - return done(); - }); - }, true); -}; - Chain.prototype.syncHeight = function syncHeight(callback, force) { var self = this; var chainHeight; @@ -1303,13 +1344,6 @@ Chain.prototype.syncHeight = function syncHeight(callback, force) { }); }; -Chain.prototype.resetTime = function resetTime(ts) { - var entry = this.byTime(ts); - if (!entry) - return; - return this.resetHeight(entry.height); -}; - Chain.prototype.resetTime = function resetTime(ts, callback, force) { var self = this; @@ -1996,7 +2030,7 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { Chain.prototype.getCurrentTarget = function getCurrentTarget(callback) { if (!this.tip) return callback(null, utils.toCompact(network.powLimit)); - return this.getTarget(this.tip, null, callback); + return this.getTargetAsync(this.tip, null, callback); }; Chain.prototype.getTargetAsync = function getTarget(last, block, callback) { diff --git a/lib/bcoin/chainblock.js b/lib/bcoin/chainblock.js index 7896c6c6..3a08b018 100644 --- a/lib/bcoin/chainblock.js +++ b/lib/bcoin/chainblock.js @@ -48,11 +48,6 @@ ChainBlock.prototype.getProof = function getProof() { }; ChainBlock.prototype.getChainwork = function getChainwork(prev) { - // Workaround for genesis block - // being added _in_ chaindb. - if (!this.chain.db) - return this.getProof(); - return (prev ? prev.chainwork : new bn(0)).add(this.getProof()); }; @@ -183,10 +178,10 @@ ChainBlock.prototype._alloc = function _alloc(max, callback) { if (this.previous.length >= max) return callback(); - if (!this.chain.db.cacheHash.has(entry.prevBlock)) + if (!this.chain.db.hasCache(entry.prevBlock)) break; - entry = this.chain.db.cacheHash.get(entry.prevBlock); + entry = this.chain.db.getCache(entry.prevBlock); } (function next(err, entry) { diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index fd14d44c..db1e5b0b 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -170,9 +170,16 @@ ChainDB.prototype.getCache = function getCache(hash) { }; ChainDB.prototype.getHeight = function getHeight(hash, callback) { + if (hash == null || hash < 0) + return callback(null, -1); + if (typeof hash === 'number') return callback(null, hash); + // When prevBlock=zero-hash + if (+hash === 0) + return callback(null, -1); + if (this.cacheHash.has(hash)) return callback(null, this.cacheHash.get(hash).height); @@ -191,6 +198,9 @@ ChainDB.prototype.getHeight = function getHeight(hash, callback) { }; ChainDB.prototype.getHash = function getHash(height, callback) { + if (height == null || height < 0) + return callback(null, null); + if (typeof height === 'string') return callback(null, height); @@ -202,7 +212,7 @@ ChainDB.prototype.getHash = function getHash(height, callback) { return callback(err); if (hash == null) - return callback(); + return callback(null, null); return callback(null, utils.toHex(hash)); }); @@ -351,16 +361,20 @@ ChainDB.prototype.getTip = function getTip(callback) { }; ChainDB.prototype.remove = function remove(block, callback, emit) { + var self = this; var blocks = []; - var entry; + var entry, hash, height; - this.getBoth(block, function(err, hash, height) { + this.get(block, function(err, data) { if (err) return callback(err); - if (hash == null) + if (!data) return callback(); + hash = data.hash; + height = data.height; + (function next(err, nextHash) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -425,6 +439,18 @@ ChainDB.prototype.remove = function remove(block, callback, emit) { } }; +ChainDB.prototype.getNextHash = function getNextHash(hash, callback) { + return this.get('c/n/' + hash, function(err, nextHash) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + if (!nextHash) + return callback(); + + return callback(null, utils.toHex(nextHash)); + }); +}; + ChainDB.prototype.resetHeight = function resetHeight(height, callback, emit) { var self = this; @@ -433,6 +459,19 @@ ChainDB.prototype.resetHeight = function resetHeight(height, callback, emit) { return this.removeEntry(height + 1, callback, emit); }; +ChainDB.prototype.resetHash = function resetHash(hash, callback, emit) { + var self = this; + return this.getNextHash(hash, function(err, hash) { + if (err) + return callback(err); + + if (!hash) + return callback(); + + return self.remove(hash, callback, emit); + }); +}; + ChainDB.prototype.has = function has(height, callback) { if (height == null || height < 0) return callback(null, false); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index d93988f4..9aa77329 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -727,7 +727,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { self.emit('chain-progress', self.chain.getProgress(), peer); - if (self.chain.height % 20 === 0) { + if (self.chain.total % 20 === 0) { utils.debug( 'Status: tip=%s ts=%s height=%d blocks=%d orphans=%d active=%d' + ' queue=%d target=%s peers=%d pending=%d highest=%d jobs=%d', @@ -738,7 +738,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { self.chain.orphan.count, self.request.activeBlocks, peer.queue.block.length, - 0, + block.bits, self.peers.all.length, self.chain.pending.length, self.chain.bestHeight, diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 06692234..7bd97895 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -208,7 +208,7 @@ exports.block = { maxSigops: 1000000 / 50, maxSigopsCost: 4000000 / 50, maxOrphanTx: 1000000 / 100, - medianTimeSpan: 11, + medianTimespan: 11, bip16time: 1333238400 };