diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index d30119a7..ba7c0499 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -40,7 +40,6 @@ var DUMMY = new Buffer([0]); * C[addr-hash][hash][index] -> dummy (coin by address) * W+T[witaddr-hash][hash] -> dummy (tx by address) * W+C[witaddr-hash][hash][index] -> dummy (coin by address) - * q[height] -> block hash to be pruned */ var layout = { @@ -69,9 +68,6 @@ var layout = { u: function u(hash) { return pair(0x75, hash); }, - q: function q(height) { - return ipair(0x71, height); - }, T: function T(address, hash) { var len = address.length; var key; @@ -228,11 +224,11 @@ ChainDB.prototype._open = co(function* open() { yield this.db.checkVersion('V', 1); - state = yield this.db.get(layout.R); + state = yield this.getState(); if (state) { // Grab the chainstate if we have one. - this.state = ChainState.fromRaw(state); + this.state = state; } else { // Otherwise write the genesis block. // (We assume this database is fresh). @@ -482,7 +478,7 @@ ChainDB.prototype.getBoth = co(function* getBoth(block) { if (hash == null) height = -1; - return [hash, height]; + return new BlockPair(hash, height); } height = yield this.getHeight(hash); @@ -490,7 +486,7 @@ ChainDB.prototype.getBoth = co(function* getBoth(block) { if (height === -1) hash = null; - return [hash, height]; + return new BlockPair(hash, height); }); /** @@ -554,26 +550,39 @@ ChainDB.prototype.get = co(function* get(hash) { */ ChainDB.prototype.save = co(function* save(entry, block, view) { + this.start(); + try { + yield this._save(entry, block, view); + } catch (e) { + this.drop(); + throw e; + } + yield this.commit(); +}); + +/** + * Save an entry without a batch. + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView?} view + * @returns {Promise} + */ + +ChainDB.prototype._save = co(function* save(entry, block, view) { var hash = block.hash(); var height = new Buffer(4); height.writeUInt32LE(entry.height, 0, true); - this.start(); - this.put(layout.h(hash), height); this.put(layout.e(hash), entry.toRaw()); this.cacheHash.set(entry.hash, entry); if (!view) { - try { - yield this.saveBlock(block); - } catch (e) { - this.drop(); - throw e; - } - return yield this.commit(); + yield this.saveBlock(block); + return; } this.cacheHeight.set(entry.height, entry); @@ -581,16 +590,9 @@ ChainDB.prototype.save = co(function* save(entry, block, view) { this.put(layout.n(entry.prevBlock), hash); this.put(layout.H(entry.height), hash); - try { - yield this.saveBlock(block, view); - } catch (e) { - this.drop(); - throw e; - } + yield this.saveBlock(block, view); this.put(layout.R, this.pending.commit(hash)); - - yield this.commit(); }); /** @@ -602,19 +604,50 @@ ChainDB.prototype.getTip = function getTip() { return this.get(this.state.hash); }; +/** + * Retrieve the tip entry from the tip record. + * @returns {Promise} - Returns {@link ChainEntry}. + */ + +ChainDB.prototype.getState = co(function* getState() { + var data = yield this.db.get(layout.R); + + if (!data) + return; + + return ChainState.fromRaw(data); +}); + /** * Reconnect the block to the chain. * @param {ChainEntry} entry * @param {Block} block * @param {CoinView} view - * @returns {Promise} - - * Returns [Error, {@link ChainEntry}, {@link Block}]. + * @returns {Promise} */ ChainDB.prototype.reconnect = co(function* reconnect(entry, block, view) { - var hash = block.hash(); - this.start(); + try { + yield this._reconnect(entry, block, view); + } catch (e) { + this.drop(); + throw e; + } + yield this.commit(); +}); + +/** + * Reconnect block without a batch. + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + +ChainDB.prototype._reconnect = co(function* reconnect(entry, block, view) { + var hash = block.hash(); this.put(layout.n(entry.prevBlock), hash); this.put(layout.H(entry.height), hash); @@ -622,31 +655,46 @@ ChainDB.prototype.reconnect = co(function* reconnect(entry, block, view) { this.cacheHash.set(entry.hash, entry); this.cacheHeight.set(entry.height, entry); - try { - yield this.connectBlock(block, view); - } catch (e) { - this.drop(); - throw e; - } + yield this.connectBlock(block, view); this.put(layout.R, this.pending.commit(hash)); - yield this.commit(); - return block; }); /** * Disconnect block from the chain. * @param {ChainEntry} entry - * @returns {Promise} - - * Returns [Error, {@link ChainEntry}, {@link Block}]. + * @returns {Promise} */ ChainDB.prototype.disconnect = co(function* disconnect(entry) { var block; this.start(); + + try { + block = yield this._disconnect(entry); + } catch (e) { + this.drop(); + throw e; + } + + yield this.commit(); + + return block; +}); + +/** + * Disconnect block without a batch. + * @private + * @param {ChainEntry} entry + * @returns {Promise} + */ + +ChainDB.prototype._disconnect = co(function* disconnect(entry) { + var block; + this.del(layout.n(entry.prevBlock)); this.del(layout.H(entry.height)); @@ -654,33 +702,18 @@ ChainDB.prototype.disconnect = co(function* disconnect(entry) { if (this.options.spv) { this.put(layout.R, this.pending.commit(entry.prevBlock)); - yield this.commit(); return entry.toHeaders(); } - try { - block = yield this.getBlock(entry.hash); - } catch (e) { - this.drop(); - throw e; - } + block = yield this.getBlock(entry.hash); - if (!block) { - this.drop(); + if (!block) throw new Error('Block not found.'); - } - try { - yield this.disconnectBlock(block); - } catch (e) { - this.drop(); - throw e; - } + yield this.disconnectBlock(block); this.put(layout.R, this.pending.commit(entry.prevBlock)); - yield this.commit(); - return block; }); @@ -782,14 +815,8 @@ ChainDB.prototype.reset = co(function* reset(block) { */ ChainDB.prototype.has = co(function* has(block) { - var items, hash; - - checkHash(block); - - items = yield this.getBoth(block); - hash = items[0]; - - return hash != null; + var item = yield this.getBoth(block); + return item.hash != null; }); /** @@ -802,16 +829,14 @@ ChainDB.prototype.has = co(function* has(block) { ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) { if (this.options.spv) - return block; + return; this.put(layout.b(block.hash()), block.toRaw()); if (!view) - return block; + return; yield this.connectBlock(block, view); - - return block; }); /** @@ -844,12 +869,12 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { var hashes, address, hash, coins, raw; if (this.options.spv) - return block; + return; // Genesis block's coinbase is unspendable. if (this.chain.isGenesis(block)) { this.pending.connect(block); - return block; + return; } this.pending.connect(block); @@ -927,8 +952,6 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { this.put(layout.u(block.hash()), undo.render()); yield this.pruneBlock(block); - - return block; }); /** @@ -942,7 +965,7 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { var hashes, address, hash, coins, raw; if (this.options.spv) - return block; + return; view = yield this.getUndoView(block); @@ -1021,8 +1044,6 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { } this.del(layout.u(block.hash())); - - return block; }); /** @@ -1464,22 +1485,19 @@ ChainDB.prototype.getUndoView = co(function* getUndoView(block) { */ ChainDB.prototype.getBlock = co(function* getBlock(hash) { - var items = yield this.getBoth(hash); + var item = yield this.getBoth(hash); var height, data, block; - hash = items[0]; - height = items[1]; - - if (!hash) + if (!item.hash) return; - data = yield this.db.get(layout.b(hash)); + data = yield this.db.get(layout.b(item.hash)); if (!data) return; block = Block.fromRaw(data); - block.setHeight(height); + block.setHeight(item.height); return block; }); @@ -1519,7 +1537,7 @@ ChainDB.prototype.hasCoins = function hasCoins(hash) { */ ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) { - var futureHeight, key, hash; + var height, hash; if (this.options.spv) return; @@ -1527,21 +1545,16 @@ ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) { if (!this.prune) return; - if (block.height <= this.network.block.pruneAfterHeight) + height = block.height - this.keepBlocks; + + if (height <= this.network.block.pruneAfterHeight) return; - futureHeight = block.height + this.keepBlocks; - - this.put(layout.q(futureHeight), block.hash()); - - key = layout.q(block.height); - - hash = yield this.db.get(key); + hash = yield this.getHash(height); if (!hash) return; - this.del(key); this.del(layout.b(hash)); this.del(layout.u(hash)); }); @@ -1654,6 +1667,11 @@ function checkHash(hash) { 'Must pass in height or hash.'); } +function BlockPair(hash, height) { + this.hash = hash; + this.height = height; +} + /* * Expose */