From ee19c7fee60bfb9102f858be8539d64f7b834359 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 10 Nov 2016 18:13:31 -0800 Subject: [PATCH] chaindb: index all tips. --- lib/chain/browser.js | 6 ++++ lib/chain/chaindb.js | 85 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/lib/chain/browser.js b/lib/chain/browser.js index 97ff74d7..ff6f7b3f 100644 --- a/lib/chain/browser.js +++ b/lib/chain/browser.js @@ -24,6 +24,9 @@ var layout = { n: function n(hash) { return 'n' + hex(hash); }, + a: function a(hash) { + return 'a' + hex(hash); + }, b: function b(hash) { return 'b' + hex(hash); }, @@ -52,6 +55,9 @@ var layout = { return 'C' + address + hex(hash) + pad32(index); }, + aa: function aa(key) { + return key.slice(1, 65); + }, Cc: function Cc(key) { var hash, index; diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index cb8353f5..b69a6fc0 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -36,6 +36,7 @@ var DUMMY = new Buffer([0]); * h[hash] -> height * H[height] -> hash * n[hash] -> next hash + * a[hash] -> tip index * b[hash] -> block * t[hash] -> extended tx * c[hash] -> coins @@ -61,6 +62,9 @@ var layout = { n: function n(hash) { return pair(0x6e, hash); }, + a: function a(hash) { + return pair(0x61, hash); + }, b: function b(hash) { return pair(0x62, hash); }, @@ -117,6 +121,9 @@ var layout = { return key; }, + aa: function aa(key) { + return key.toString('hex', 1, 33); + }, Cc: function Cc(key) { var hash, index; @@ -660,6 +667,19 @@ ChainDB.prototype.getEntries = function getEntries() { }); }; +/** + * Get all tip hashes. + * @returns {Promise} - Returns {@link Hash}[]. + */ + +ChainDB.prototype.getTips = function getTips() { + return this.db.keys({ + gte: layout.a(constants.ZERO_HASH), + lte: layout.a(constants.MAX_HASH), + parse: layout.aa + }); +}; + /** * Get a coin (unspents only). * @param {Hash} hash @@ -1209,23 +1229,34 @@ ChainDB.prototype.save = co(function* save(entry, block, view) { ChainDB.prototype._save = co(function* save(entry, block, view) { var hash = block.hash(); + // Hash->height index this.put(layout.h(hash), U32(entry.height)); - this.put(layout.e(hash), entry.toRaw()); + // Entry data + this.put(layout.e(hash), entry.toRaw()); this.cacheHash.push(entry.hash, entry); + // Tip index + this.del(layout.a(entry.prevBlock)); + this.put(layout.a(hash), DUMMY); + if (!view) { + // Save block data yield this.saveBlock(block); return; } + // Hash->next-block index + this.put(layout.n(entry.prevBlock), hash); + + // Height->hash index + this.put(layout.H(entry.height), hash); this.cacheHeight.push(entry.height, entry); - this.put(layout.n(entry.prevBlock), hash); - this.put(layout.H(entry.height), hash); - + // Connect block and save data yield this.saveBlock(block, view); + // New chain state this.put(layout.R, this.pending.commit(hash)); }); @@ -1341,6 +1372,12 @@ ChainDB.prototype.reset = co(function* reset(block) { if (this.options.prune) throw new Error('Cannot reset when pruned.'); + // We need to remove all alternate + // chains first. This is ugly, but + // it's the only safe way to reset + // the chain. + yield this.removeChains(); + tip = yield this.getTip(); assert(tip); @@ -1357,6 +1394,9 @@ ChainDB.prototype.reset = co(function* reset(block) { assert(!tip.isGenesis()); + this.del(layout.a(tip.hash)); + this.put(layout.a(tip.prevBlock), DUMMY); + this.del(layout.H(tip.height)); this.del(layout.h(tip.hash)); this.del(layout.e(tip.hash)); @@ -1384,25 +1424,39 @@ ChainDB.prototype.reset = co(function* reset(block) { }); /** - * Remove an alternate chain. - * @param {ChainEntry} tip - * @param {ChainEntry} entry + * Remove all alternate chains. * @returns {Promise} */ -ChainDB.prototype.removeChain = co(function* removeChain(tip, entry) { - this.logger.debug('Removing alternate chain: %s->%s', tip.rhash, entry.rhash); +ChainDB.prototype.removeChains = co(function* removeChains() { + var tips = yield this.getTips(); + var i; + + for (i = 0; i < tips.length; i++) + yield this.removeChain(tips[i]); +}); + +/** + * Remove an alternate chain. + * @param {Hash} hash - Alternate chain tip. + * @returns {Promise} + */ + +ChainDB.prototype.removeChain = co(function* removeChain(hash) { + var tip = yield this.get(hash); + + if (!tip) + throw new Error('Alternate chain tip not found.'); + + this.logger.debug('Removing alternate chain: %s.', tip.rhash); this.start(); - for (;;) } - if (tip.hash === entry.hash) + while (tip) { + if (yield tip.isMainChain()) break; - if (tip.isGenesis()) { - this.drop(); - throw new Error('Target entry not in chain.'); - } + assert(!tip.isGenesis()); this.del(layout.h(tip.hash)); this.del(layout.e(tip.hash)); @@ -1411,7 +1465,6 @@ ChainDB.prototype.removeChain = co(function* removeChain(tip, entry) { this.cacheHash.unpush(tip.hash); tip = yield this.get(tip.prevBlock); - assert(tip); } yield this.commit();