From fe2f2fcc2a9fe40e1deb1d8e87afab9fbfc8906f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 4 Oct 2016 22:41:08 -0700 Subject: [PATCH] chaindb: reorganize some functions. --- lib/chain/browser.js | 3 - lib/chain/chaindb.js | 1126 +++++++++++++++++++++--------------------- 2 files changed, 563 insertions(+), 566 deletions(-) diff --git a/lib/chain/browser.js b/lib/chain/browser.js index 79fca248..c33c3bba 100644 --- a/lib/chain/browser.js +++ b/lib/chain/browser.js @@ -35,9 +35,6 @@ var layout = { u: function u(hash) { return 'u' + hex(hash); }, - q: function q(height) { - return 'q' + pad32(height); - }, T: function T(address, hash) { address = hex(address); diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index ba7c0499..5d34aa9b 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -538,6 +538,569 @@ ChainDB.prototype.get = co(function* get(hash) { return entry; }); +/** + * Test whether the chain contains a block in the + * main chain or an alternate chain. Alternate chains will only + * be tested if the lookup is done by hash. + * @param {Hash|Number} block - Hash or height. + * @returns {Promise} - Returns Boolean. + */ + +ChainDB.prototype.has = co(function* has(block) { + var item = yield this.getBoth(block); + return item.hash != null; +}); + +/** + * Retrieve the tip entry from the tip record. + * @returns {Promise} - Returns {@link ChainEntry}. + */ + +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); +}); + +/** + * Get the _next_ block hash (does not work by height). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Hash}. + */ + +ChainDB.prototype.getNextHash = co(function* getNextHash(hash) { + var data = yield this.db.get(layout.n(hash)); + + if (!data) + return; + + return data.toString('hex'); +}); + +/** + * Check to see if a block is on the main chain. + * @param {ChainEntry|Hash} hash + * @returns {Promise} - Returns Boolean. + */ + +ChainDB.prototype.isMainChain = co(function* isMainChain(hash) { + var query, height, existing; + + if (hash instanceof ChainEntry) { + query = hash.height; + hash = hash.hash; + } else { + query = hash; + } + + if (hash === this.chain.tip.hash + || hash === this.network.genesis.hash) { + return true; + } + + height = yield this.getHeight(query); + existing = yield this.getHash(height); + + if (!existing) + return false; + + return hash === existing; +}); + +/** + * Get all entries. + * @returns {Promise} - Returns {@link ChainEntry}[]. + */ + +ChainDB.prototype.getEntries = function getEntries() { + var self = this; + return this.db.values({ + gte: layout.e(constants.ZERO_HASH), + lte: layout.e(constants.MAX_HASH), + parse: function(key, value) { + return ChainEntry.fromRaw(self.chain, value); + } + }); +}; + +/** + * Get a coin (unspents only). + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ + +ChainDB.prototype.getCoin = co(function* getCoin(hash, index) { + var coins = this.coinCache.get(hash); + + if (coins) + return Coins.parseCoin(coins, hash, index); + + coins = yield this.db.get(layout.c(hash)); + + if (!coins) + return; + + this.coinCache.set(hash, coins); + + return Coins.parseCoin(coins, hash, index); +}); + +/** + * Get coins (unspents only). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Coins}. + */ + +ChainDB.prototype.getCoins = co(function* getCoins(hash) { + var coins = this.coinCache.get(hash); + + if (coins) + return Coins.fromRaw(coins, hash); + + coins = yield this.db.get(layout.c(hash)); + + if (!coins) + return; + + this.coinCache.set(hash, coins); + + return Coins.fromRaw(coins, hash); +}); + +/** + * Check whether coins are still unspent. Necessary for bip30. + * @see https://bitcointalk.org/index.php?topic=67738.0 + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ + +ChainDB.prototype.hasCoins = function hasCoins(hash) { + return this.db.has(layout.c(hash)); +}; + +/** + * Get a view of the existing coins necessary to verify a block. + * @param {Block} block + * @returns {Promise} - Returns {@link CoinView}. + */ + +ChainDB.prototype.getCoinView = co(function* getCoinView(block, callback) { + var view = new CoinView(); + var prevout = block.getPrevout(); + var i, prev, coins; + + for (i = 0; i < prevout.length; i++) { + prev = prevout[i]; + coins = yield this.getCoins(prev); + if (coins) + view.add(coins); + } + + return view; +}); + +/** + * Get coins necessary to be resurrected during a reorg. + * @param {Hash} hash + * @returns {Promise} - Returns {@link Coin}[]. + */ + +ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) { + var data = yield this.db.get(layout.u(hash)); + var p, coins; + + if (!data) + return; + + p = new BufferReader(data); + coins = []; + + while (p.left()) + coins.push(Coin.fromRaw(p)); + + return coins; +}); + +/** + * Get a coin view containing unspent coins as + * well as the coins to be resurrected for a reorg. + * (Note: fills block with undo coins). + * @param {Block} block + * @returns {Promise} - Returns {@link CoinView}. + */ + +ChainDB.prototype.getUndoView = co(function* getUndoView(block) { + var i, j, k, tx, input, coin, view, coins; + + view = yield this.getCoinView(block); + coins = yield this.getUndoCoins(block.hash()); + + if (!coins) + return view; + + for (i = 0, k = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + + if (tx.isCoinbase()) + continue; + + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + coin = coins[k++]; + coin.hash = input.prevout.hash; + coin.index = input.prevout.index; + input.coin = coin; + view.addCoin(coin); + } + } + + return view; +}); + +/** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ + +ChainDB.prototype.getBlock = co(function* getBlock(hash) { + var item = yield this.getBoth(hash); + var height, data, block; + + if (!item.hash) + return; + + data = yield this.db.get(layout.b(item.hash)); + + if (!data) + return; + + block = Block.fromRaw(data); + block.setHeight(item.height); + + return block; +}); + +/** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ + +ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) { + var hash = yield this.getHash(block); + + if (!hash) + return; + + return yield this.db.get(layout.b(hash)); +}); + +/** + * Get a block and fill it with coins (historical). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ + +ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) { + var block = yield this.getBlock(hash); + + if (!block) + return; + + yield this.getUndoView(block); + + return block; +}); + +/** + * Fill a transaction with coins (only unspents). + * @param {TX} tx + * @returns {Promise} - Returns {@link TX}. + */ + +ChainDB.prototype.fillCoins = co(function* fillCoins(tx) { + var i, input, prevout, coin; + + if (tx.isCoinbase()) + return tx; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + + if (input.coin) + continue; + + coin = yield this.getCoin(prevout.hash, prevout.index); + + if (coin) + input.coin = coin; + } + + return tx; +}); + +/** + * Fill a transaction with coins (all historical coins). + * @param {TX} tx + * @returns {Promise} - Returns {@link TX}. + */ + +ChainDB.prototype.fillHistory = co(function* fillHistory(tx) { + var i, input, ptx; + + if (!this.options.indexTX) + return tx; + + if (tx.isCoinbase()) + return tx; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + + if (input.coin) + continue; + + ptx = yield this.getTX(input.prevout.hash); + + if (ptx) + input.coin = Coin.fromTX(ptx, input.prevout.index); + } + + return tx; +}); + +/** + * Retrieve a transaction (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ + +ChainDB.prototype.getTX = co(function* getTX(hash) { + var data; + + if (!this.options.indexTX) + return; + + data = yield this.db.get(layout.t(hash)); + + if (!data) + return; + + return TX.fromExtended(data); +}); + +/** + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ + +ChainDB.prototype.hasTX = function hasTX(hash) { + if (!this.options.indexTX) + return Promise.resolve(null); + + return this.db.has(layout.t(hash)); +}; + +/** + * Get a transaction and fill it with coins (historical). + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ + +ChainDB.prototype.getFullTX = co(function* getFullTX(hash) { + var tx; + + if (!this.options.indexTX) + return; + + tx = yield this.getTX(hash); + + if (!tx) + return; + + yield this.fillHistory(tx); + + return tx; +}); + +/** + * Get all coins pertinent to an address. + * @param {Address[]} addresses + * @returns {Promise} - Returns {@link Coin}[]. + */ + +ChainDB.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) { + var coins = []; + var i, j, address, hash, keys, key, coin; + + if (!this.options.indexAddress) + return coins; + + if (!Array.isArray(addresses)) + addresses = [addresses]; + + for (i = 0; i < addresses.length; i++) { + address = addresses[i]; + hash = Address.getHash(address); + + if (!hash) + continue; + + keys = yield this.db.keys({ + gte: layout.C(hash, constants.ZERO_HASH, 0), + lte: layout.C(hash, constants.MAX_HASH, 0xffffffff), + parse: layout.Cc + }); + + for (j = 0; j < keys.length; j++) { + key = keys[j]; + coin = yield this.getCoin(key[0], key[1]); + + if (coin) + coins.push(coin); + } + } + + return coins; +}); + +/** + * Get all transaction hashes to an address. + * @param {Address[]} addresses + * @returns {Promise} - Returns {@link Hash}[]. + */ + +ChainDB.prototype.getHashesByAddress = co(function* getHashesByAddress(addresses) { + var hashes = {}; + var i, address, hash; + + if (!this.options.indexTX || !this.options.indexAddress) + return []; + + for (i = 0; i < addresses.length; i++) { + address = addresses[i]; + hash = Address.getHash(address); + + if (!hash) + continue; + + yield this.db.keys({ + gte: layout.T(hash, constants.ZERO_HASH), + lte: layout.T(hash, constants.MAX_HASH), + parse: function(key) { + var hash = layout.Tt(key); + hashes[hash] = true; + } + }); + } + + return Object.keys(hashes); +}); + +/** + * Get all transactions pertinent to an address. + * @param {Address[]} addresses + * @returns {Promise} - Returns {@link TX}[]. + */ + +ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { + var txs = []; + var i, hashes, hash, tx; + + if (!this.options.indexTX || !this.options.indexAddress) + return txs; + + if (!Array.isArray(addresses)) + addresses = [addresses]; + + hashes = yield this.getHashesByAddress(addresses); + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + tx = yield this.getTX(hash); + if (tx) + txs.push(tx); + } + + return txs; +}); + +/** + * Scan the blockchain for transactions containing specified address hashes. + * @param {Hash} start - Block hash to start at. + * @param {Hash[]} hashes - Address hashes. + * @param {Function} iter - Iterator. Accepts ({@link TX}, {@link Function}). + * @returns {Promise} + */ + +ChainDB.prototype.scan = co(function* scan(start, filter, iter) { + var total = 0; + var i, j, entry, hashes, hash, tx, txs, block; + + if (this.options.spv) + throw new Error('Cannot scan in spv mode.'); + + if (start == null) + start = this.network.genesis.hash; + + if (typeof start === 'number') + this.logger.info('Scanning from height %d.', start); + else + this.logger.info('Scanning from block %s.', utils.revHex(start)); + + if (Array.isArray(filter)) + filter = utils.toMap(filter); + + entry = yield this.getEntry(start); + + while (entry) { + block = yield this.getFullBlock(entry.hash); + total++; + + if (!block) + throw new Error('Block not found.'); + + this.logger.info( + 'Scanning block %s (%d).', + entry.rhash, block.height); + + txs = []; + + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + hashes = tx.getHashes('hex'); + + for (j = 0; j < hashes.length; j++) { + hash = hashes[j]; + if (filter[hash]) { + txs.push(tx); + break; + } + } + } + + yield iter(entry, txs); + entry = yield entry.getNext(); + } + + this.logger.info('Finished scanning %d blocks.', total); +}); + /** * Save an entry to the database and optionally * connect it as the tip. Note that this method @@ -595,29 +1158,6 @@ ChainDB.prototype._save = co(function* save(entry, block, view) { this.put(layout.R, this.pending.commit(hash)); }); -/** - * Retrieve the tip entry from the tip record. - * @returns {Promise} - Returns {@link ChainEntry}. - */ - -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 @@ -717,51 +1257,6 @@ ChainDB.prototype._disconnect = co(function* disconnect(entry) { return block; }); -/** - * Get the _next_ block hash (does not work by height). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Hash}. - */ - -ChainDB.prototype.getNextHash = co(function* getNextHash(hash) { - var data = yield this.db.get(layout.n(hash)); - - if (!data) - return; - - return data.toString('hex'); -}); - -/** - * Check to see if a block is on the main chain. - * @param {ChainEntry|Hash} hash - * @returns {Promise} - Returns Boolean. - */ - -ChainDB.prototype.isMainChain = co(function* isMainChain(hash) { - var query, height, existing; - - if (hash instanceof ChainEntry) { - query = hash.height; - hash = hash.hash; - } else { - query = hash; - } - - if (hash === this.chain.tip.hash - || hash === this.network.genesis.hash) { - return true; - } - - height = yield this.getHeight(query); - existing = yield this.getHash(height); - - if (!existing) - return false; - - return hash === existing; -}); - /** * Reset the chain to a height or hash. Useful for replaying * the blockchain download for SPV. @@ -806,19 +1301,6 @@ ChainDB.prototype.reset = co(function* reset(block) { } }); -/** - * Test whether the chain contains a block in the - * main chain or an alternate chain. Alternate chains will only - * be tested if the lookup is done by hash. - * @param {Hash|Number} block - Hash or height. - * @returns {Promise} - Returns Boolean. - */ - -ChainDB.prototype.has = co(function* has(block) { - var item = yield this.getBoth(block); - return item.hash != null; -}); - /** * Save a block (not an entry) to the * database and potentially connect the inputs. @@ -1046,488 +1528,6 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { this.del(layout.u(block.hash())); }); -/** - * Fill a transaction with coins (only unspents). - * @param {TX} tx - * @returns {Promise} - Returns {@link TX}. - */ - -ChainDB.prototype.fillCoins = co(function* fillCoins(tx) { - var i, input, prevout, coin; - - if (tx.isCoinbase()) - return tx; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - prevout = input.prevout; - - if (input.coin) - continue; - - coin = yield this.getCoin(prevout.hash, prevout.index); - - if (coin) - input.coin = coin; - } - - return tx; -}); - -/** - * Fill a transaction with coins (all historical coins). - * @param {TX} tx - * @returns {Promise} - Returns {@link TX}. - */ - -ChainDB.prototype.fillHistory = co(function* fillHistory(tx) { - var i, input, ptx; - - if (!this.options.indexTX) - return tx; - - if (tx.isCoinbase()) - return tx; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - - if (input.coin) - continue; - - ptx = yield this.getTX(input.prevout.hash); - - if (ptx) - input.coin = Coin.fromTX(ptx, input.prevout.index); - } - - return tx; -}); - -/** - * Get a coin (unspents only). - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ - -ChainDB.prototype.getCoin = co(function* getCoin(hash, index) { - var coins = this.coinCache.get(hash); - - if (coins) - return Coins.parseCoin(coins, hash, index); - - coins = yield this.db.get(layout.c(hash)); - - if (!coins) - return; - - this.coinCache.set(hash, coins); - - return Coins.parseCoin(coins, hash, index); -}); - -/** - * Get coins (unspents only). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Coins}. - */ - -ChainDB.prototype.getCoins = co(function* getCoins(hash) { - var coins = this.coinCache.get(hash); - - if (coins) - return Coins.fromRaw(coins, hash); - - coins = yield this.db.get(layout.c(hash)); - - if (!coins) - return; - - this.coinCache.set(hash, coins); - - return Coins.fromRaw(coins, hash); -}); - -/** - * Scan the blockchain for transactions containing specified address hashes. - * @param {Hash} start - Block hash to start at. - * @param {Hash[]} hashes - Address hashes. - * @param {Function} iter - Iterator. Accepts ({@link TX}, {@link Function}). - * @returns {Promise} - */ - -ChainDB.prototype.scan = co(function* scan(start, filter, iter) { - var total = 0; - var i, j, entry, hashes, hash, tx, txs, block; - - if (this.options.spv) - throw new Error('Cannot scan in spv mode.'); - - if (start == null) - start = this.network.genesis.hash; - - if (typeof start === 'number') - this.logger.info('Scanning from height %d.', start); - else - this.logger.info('Scanning from block %s.', utils.revHex(start)); - - if (Array.isArray(filter)) - filter = utils.toMap(filter); - - entry = yield this.getEntry(start); - - while (entry) { - block = yield this.getFullBlock(entry.hash); - total++; - - if (!block) - throw new Error('Block not found.'); - - this.logger.info( - 'Scanning block %s (%d).', - entry.rhash, block.height); - - txs = []; - - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - hashes = tx.getHashes('hex'); - - for (j = 0; j < hashes.length; j++) { - hash = hashes[j]; - if (filter[hash]) { - txs.push(tx); - break; - } - } - } - - yield iter(entry, txs); - entry = yield entry.getNext(); - } - - this.logger.info('Finished scanning %d blocks.', total); -}); - -/** - * Retrieve a transaction (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ - -ChainDB.prototype.getTX = co(function* getTX(hash) { - var data; - - if (!this.options.indexTX) - return; - - data = yield this.db.get(layout.t(hash)); - - if (!data) - return; - - return TX.fromExtended(data); -}); - -/** - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ - -ChainDB.prototype.hasTX = function hasTX(hash) { - if (!this.options.indexTX) - return Promise.resolve(null); - - return this.db.has(layout.t(hash)); -}; - -/** - * Get all coins pertinent to an address. - * @param {Address[]} addresses - * @returns {Promise} - Returns {@link Coin}[]. - */ - -ChainDB.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) { - var coins = []; - var i, j, address, hash, keys, key, coin; - - if (!this.options.indexAddress) - return coins; - - if (!Array.isArray(addresses)) - addresses = [addresses]; - - for (i = 0; i < addresses.length; i++) { - address = addresses[i]; - hash = Address.getHash(address); - - if (!hash) - continue; - - keys = yield this.db.keys({ - gte: layout.C(hash, constants.ZERO_HASH, 0), - lte: layout.C(hash, constants.MAX_HASH, 0xffffffff), - parse: layout.Cc - }); - - for (j = 0; j < keys.length; j++) { - key = keys[j]; - coin = yield this.getCoin(key[0], key[1]); - - if (coin) - coins.push(coin); - } - } - - return coins; -}); - -/** - * Get all entries. - * @returns {Promise} - Returns {@link ChainEntry}[]. - */ - -ChainDB.prototype.getEntries = function getEntries() { - var self = this; - return this.db.values({ - gte: layout.e(constants.ZERO_HASH), - lte: layout.e(constants.MAX_HASH), - parse: function(key, value) { - return ChainEntry.fromRaw(self.chain, value); - } - }); -}; - -/** - * Get all transaction hashes to an address. - * @param {Address[]} addresses - * @returns {Promise} - Returns {@link Hash}[]. - */ - -ChainDB.prototype.getHashesByAddress = co(function* getHashesByAddress(addresses) { - var hashes = {}; - var i, address, hash; - - if (!this.options.indexTX || !this.options.indexAddress) - return []; - - for (i = 0; i < addresses.length; i++) { - address = addresses[i]; - hash = Address.getHash(address); - - if (!hash) - continue; - - yield this.db.keys({ - gte: layout.T(hash, constants.ZERO_HASH), - lte: layout.T(hash, constants.MAX_HASH), - parse: function(key) { - var hash = layout.Tt(key); - hashes[hash] = true; - } - }); - } - - return Object.keys(hashes); -}); - -/** - * Get all transactions pertinent to an address. - * @param {Address[]} addresses - * @returns {Promise} - Returns {@link TX}[]. - */ - -ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { - var txs = []; - var i, hashes, hash, tx; - - if (!this.options.indexTX || !this.options.indexAddress) - return txs; - - if (!Array.isArray(addresses)) - addresses = [addresses]; - - hashes = yield this.getHashesByAddress(addresses); - - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - tx = yield this.getTX(hash); - if (tx) - txs.push(tx); - } - - return txs; -}); - -/** - * Get a transaction and fill it with coins (historical). - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ - -ChainDB.prototype.getFullTX = co(function* getFullTX(hash) { - var tx; - - if (!this.options.indexTX) - return; - - tx = yield this.getTX(hash); - - if (!tx) - return; - - yield this.fillHistory(tx); - - return tx; -}); - -/** - * Get a block and fill it with coins (historical). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ - -ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) { - var block = yield this.getBlock(hash); - - if (!block) - return; - - yield this.getUndoView(block); - - return block; -}); - -/** - * Get a view of the existing coins necessary to verify a block. - * @param {Block} block - * @returns {Promise} - Returns {@link CoinView}. - */ - -ChainDB.prototype.getCoinView = co(function* getCoinView(block, callback) { - var view = new CoinView(); - var prevout = block.getPrevout(); - var i, prev, coins; - - for (i = 0; i < prevout.length; i++) { - prev = prevout[i]; - coins = yield this.getCoins(prev); - if (coins) - view.add(coins); - } - - return view; -}); - -/** - * Get coins necessary to be resurrected during a reorg. - * @param {Hash} hash - * @returns {Promise} - Returns {@link Coin}[]. - */ - -ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) { - var data = yield this.db.get(layout.u(hash)); - var p, coins; - - if (!data) - return; - - p = new BufferReader(data); - coins = []; - - while (p.left()) - coins.push(Coin.fromRaw(p)); - - return coins; -}); - -/** - * Get a coin view containing unspent coins as - * well as the coins to be resurrected for a reorg. - * (Note: fills block with undo coins). - * @param {Block} block - * @returns {Promise} - Returns {@link CoinView}. - */ - -ChainDB.prototype.getUndoView = co(function* getUndoView(block) { - var i, j, k, tx, input, coin, view, coins; - - view = yield this.getCoinView(block); - coins = yield this.getUndoCoins(block.hash()); - - if (!coins) - return view; - - for (i = 0, k = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - - if (tx.isCoinbase()) - continue; - - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - coin = coins[k++]; - coin.hash = input.prevout.hash; - coin.index = input.prevout.index; - input.coin = coin; - view.addCoin(coin); - } - } - - return view; -}); - -/** - * Retrieve a block from the database (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ - -ChainDB.prototype.getBlock = co(function* getBlock(hash) { - var item = yield this.getBoth(hash); - var height, data, block; - - if (!item.hash) - return; - - data = yield this.db.get(layout.b(item.hash)); - - if (!data) - return; - - block = Block.fromRaw(data); - block.setHeight(item.height); - - return block; -}); - -/** - * Retrieve a block from the database (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ - -ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) { - var hash = yield this.getHash(block); - - if (!hash) - return; - - return yield this.db.get(layout.b(hash)); -}); - -/** - * Check whether coins are still unspent. Necessary for bip30. - * @see https://bitcointalk.org/index.php?topic=67738.0 - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ - -ChainDB.prototype.hasCoins = function hasCoins(hash) { - return this.db.has(layout.c(hash)); -}; - /** * Prune a block from the chain and * add current block to the prune queue.