From 5f82c0d8c218503581758ce027bc5afa100aa641 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Sep 2017 06:02:40 -0700 Subject: [PATCH] chain: alias all db methods. remove chainentry spaghetti code. --- lib/blockchain/chain.js | 485 ++++++++++++++++++++++++++++------- lib/blockchain/chaindb.js | 230 ++++++++++------- lib/blockchain/chainentry.js | 216 ++-------------- lib/http/rpc.js | 54 ++-- lib/http/server.js | 10 +- lib/mining/miner.js | 2 +- lib/net/pool.js | 22 +- lib/node/fullnode.js | 12 +- lib/wallet/nodeclient.js | 4 +- test/chain-test.js | 54 ++-- test/node-test.js | 32 +-- 11 files changed, 641 insertions(+), 480 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index bfbdb59f3..16d7625f9 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -72,25 +72,32 @@ function Chain(options) { this.logger = this.options.logger.context('chain'); this.workers = this.options.workers; + this.db = new ChainDB(this.options); + this.locker = new Lock(true); this.invalid = new LRU(100); this.state = new DeploymentState(); - this.tip = new ChainEntry(this); + this.tip = new ChainEntry(); this.height = -1; this.synced = false; this.orphanMap = new Map(); this.orphanPrev = new Map(); - - this.db = new ChainDB(this); } Object.setPrototypeOf(Chain.prototype, AsyncObject.prototype); +/** + * Size of set to pick median time from. + * @const {Number} + * @default + */ + +Chain.MEDIAN_TIMESPAN = 11; + /** * Open the chain, wait for the database to load. - * @method * @alias Chain#open * @returns {Promise} */ @@ -146,7 +153,6 @@ Chain.prototype._close = function _close() { /** * Perform all necessary contextual verification on a block. - * @method * @private * @param {Block} block * @param {ChainEntry} prev @@ -170,7 +176,6 @@ Chain.prototype.verifyContext = async function verifyContext(block, prev, flags) /** * Perform all necessary contextual verification * on a block, without POW check. - * @method * @param {Block} block * @returns {Promise} */ @@ -187,7 +192,6 @@ Chain.prototype.verifyBlock = async function verifyBlock(block) { /** * Perform all necessary contextual verification * on a block, without POW check (no lock). - * @method * @private * @param {Block} block * @returns {Promise} @@ -208,11 +212,134 @@ Chain.prototype.isGenesis = function isGenesis(block) { return block.hash('hex') === this.network.genesis.hash; }; +/** + * Test whether the hash is in the main chain. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ + +Chain.prototype.isMainHash = function isMainHash(hash) { + return this.db.isMainHash(hash); +}; + +/** + * Test whether the entry is in the main chain. + * @param {ChainEntry} entry + * @returns {Promise} - Returns Boolean. + */ + +Chain.prototype.isMainChain = function isMainChain(entry) { + return this.db.isMainChain(entry); +}; + +/** + * Get ancestor by `height`. + * @param {ChainEntry} entry + * @param {Number} height + * @returns {Promise} - Returns ChainEntry. + */ + +Chain.prototype.getAncestor = function getAncestor(entry, height) { + return this.db.getAncestor(entry, height); +}; + +/** + * Get previous entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ + +Chain.prototype.getPrevious = function getPrevious(entry) { + return this.db.getPrevious(entry); +}; + +/** + * Get previous cached entry. + * @param {ChainEntry} entry + * @returns {ChainEntry|null} + */ + +Chain.prototype.getPrevCache = function getPrevCache(entry) { + return this.db.getPrevCache(entry); +}; + +/** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ + +Chain.prototype.getNext = function getNext(entry) { + return this.db.getNext(entry); +}; + +/** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ + +Chain.prototype.getNextEntry = function getNextEntry(entry) { + return this.db.getNextEntry(entry); +}; + +/** + * Calculate median time past. + * @param {ChainEntry} prev + * @param {Number?} time + * @returns {Promise} - Returns Number. + */ + +Chain.prototype.getMedianTime = async function getMedianTime(prev, time) { + let timespan = Chain.MEDIAN_TIMESPAN; + + const median = []; + + // In case we ever want to check + // the MTP of the _current_ block + // (necessary for BIP148). + if (time != null) { + median.push(time); + timespan -= 1; + } + + let entry = prev; + + for (let i = 0; i < timespan && entry; i++) { + median.push(entry.time); + + const cache = this.getPrevCache(entry); + + if (cache) + entry = cache; + else + entry = await this.getPrevious(entry); + } + + median.sort(cmp); + + return median[median.length >>> 1]; +}; + +/** + * Test whether the entry is potentially + * an ancestor of a checkpoint. + * @param {ChainEntry} prev + * @returns {Boolean} + */ + +Chain.prototype.isHistorical = function isHistorical(prev) { + if (this.options.checkpoints) { + if (prev.height + 1 <= this.network.lastCheckpoint) + return true; + } + return false; +}; + /** * Contextual verification for a block, including * version deployments (IsSuperMajority), versionbits, * coinbase height, finality checks. - * @method * @private * @param {Block} block * @param {ChainEntry} prev @@ -244,7 +371,7 @@ Chain.prototype.verify = async function verify(block, prev, flags) { // We can do this safely because every // block in between each checkpoint was // validated outside in the header chain. - if (prev.isHistorical()) { + if (this.isHistorical(prev)) { if (this.options.spv) return new DeploymentState(); @@ -279,7 +406,7 @@ Chain.prototype.verify = async function verify(block, prev, flags) { } // Ensure the timestamp is correct. - const mtp = await prev.getMedianTime(); + const mtp = await this.getMedianTime(prev); if (block.time <= mtp) { throw new VerifyError(block, @@ -403,7 +530,6 @@ Chain.prototype.verify = async function verify(block, prev, flags) { /** * Check all deployments on a chain, ranging from p2sh to segwit. - * @method * @param {Number} time * @param {ChainEntry} prev * @returns {Promise} - Returns {@link DeploymentState}. @@ -474,7 +600,7 @@ Chain.prototype.getDeployments = async function getDeployments(time, prev) { // assumption that deployment checks should // only ever examine the values of the // previous block (necessary for mining). - const mtp = await prev.getMedianTime(time); + const mtp = await this.getMedianTime(prev, time); if (mtp >= 1501545600 && mtp <= 1510704000) state.bip148 = true; } @@ -525,7 +651,6 @@ Chain.prototype.setDeploymentState = function setDeploymentState(state) { * Determine whether to check block for duplicate txids in blockchain * history (BIP30). If we're on a chain that has bip34 activated, we * can skip this. - * @method * @private * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki * @param {Block} block @@ -537,7 +662,7 @@ Chain.prototype.verifyDuplicates = async function verifyDuplicates(block, prev, if (this.options.spv) return; - if (prev.isHistorical()) + if (this.isHistorical(prev)) return; // BIP34 made it impossible to @@ -574,7 +699,6 @@ Chain.prototype.verifyDuplicates = async function verifyDuplicates(block, prev, * will attempt to do this on the worker pool). If * `checkpoints` is enabled, it will skip verification * for historical data. - * @method * @private * @see TX#verifyInputs * @see TX#verify @@ -592,7 +716,7 @@ Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { const interval = this.network.halvingInterval; const height = prev.height + 1; - const historical = prev.isHistorical(); + const historical = this.isHistorical(prev); let sigops = 0; let reward = 0; @@ -698,26 +822,9 @@ Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { return view; }; -/** - * Get the cached height for a hash if present. - * @private - * @param {Hash} hash - * @returns {Number} - */ - -Chain.prototype.checkHeight = function checkHeight(hash) { - const entry = this.db.getCache(hash); - - if (!entry) - return -1; - - return entry.height; -}; - /** * Find the block at which a fork ocurred. * @private - * @method * @param {ChainEntry} fork - The current chain. * @param {ChainEntry} longer - The competing chain. * @returns {Promise} @@ -726,7 +833,7 @@ Chain.prototype.checkHeight = function checkHeight(hash) { Chain.prototype.findFork = async function findFork(fork, longer) { while (fork.hash !== longer.hash) { while (longer.height > fork.height) { - longer = await longer.getPrevious(); + longer = await this.getPrevious(longer); if (!longer) throw new Error('No previous entry for new tip.'); } @@ -734,7 +841,7 @@ Chain.prototype.findFork = async function findFork(fork, longer) { if (fork.hash === longer.hash) return fork; - fork = await fork.getPrevious(); + fork = await this.getPrevious(fork); if (!fork) throw new Error('No previous entry for old tip.'); @@ -747,7 +854,6 @@ Chain.prototype.findFork = async function findFork(fork, longer) { * Reorganize the blockchain (connect and disconnect inputs). * Called when a competing chain with a higher chainwork * is received. - * @method * @private * @param {ChainEntry} competitor - The competing chain's tip. * @returns {Promise} @@ -764,7 +870,7 @@ Chain.prototype.reorganize = async function reorganize(competitor) { let entry = tip; while (entry.hash !== fork.hash) { disconnect.push(entry); - entry = await entry.getPrevious(); + entry = await this.getPrevious(entry); assert(entry); } @@ -773,7 +879,7 @@ Chain.prototype.reorganize = async function reorganize(competitor) { entry = competitor; while (entry.hash !== fork.hash) { connect.push(entry); - entry = await entry.getPrevious(); + entry = await this.getPrevious(entry); assert(entry); } @@ -805,7 +911,6 @@ Chain.prototype.reorganize = async function reorganize(competitor) { /** * Reorganize the blockchain for SPV. This * will reset the chain to the fork block. - * @method * @private * @param {ChainEntry} competitor - The competing chain's tip. * @returns {Promise} @@ -822,7 +927,7 @@ Chain.prototype.reorganizeSPV = async function reorganizeSPV(competitor) { let entry = tip; while (entry.hash !== fork.hash) { disconnect.push(entry); - entry = await entry.getPrevious(); + entry = await this.getPrevious(entry); assert(entry); } @@ -857,7 +962,6 @@ Chain.prototype.reorganizeSPV = async function reorganizeSPV(competitor) { /** * Disconnect an entry from the chain (updates the tip). - * @method * @param {ChainEntry} entry * @returns {Promise} */ @@ -871,7 +975,7 @@ Chain.prototype.disconnect = async function disconnect(entry) { block = entry.toHeaders(); } - const prev = await entry.getPrevious(); + const prev = await this.getPrevious(entry); const view = await this.db.disconnect(entry, block); assert(prev); @@ -889,7 +993,6 @@ Chain.prototype.disconnect = async function disconnect(entry) { * This will do contextual-verification on the block * (necessary because we cannot validate the inputs * in alternate chains when they come in). - * @method * @param {ChainEntry} entry * @param {Number} flags * @returns {Promise} @@ -906,7 +1009,7 @@ Chain.prototype.reconnect = async function reconnect(entry) { block = entry.toHeaders(); } - const prev = await entry.getPrevious(); + const prev = await this.getPrevious(entry); assert(prev); let view, state; @@ -940,7 +1043,6 @@ Chain.prototype.reconnect = async function reconnect(entry) { * that comes in. It may add and connect the block (main chain), * save the block without connection (alternate chain), or * reorganize the chain (a higher fork). - * @method * @private * @param {ChainEntry} entry * @param {Block} block @@ -966,7 +1068,7 @@ Chain.prototype.setBestChain = async function setBestChain(entry, block, prev, f } // Warn of unknown versionbits. - if (entry.hasUnknown()) { + if (entry.hasUnknown(this.network)) { this.logger.warning( 'Unknown version bits in block %d: %s.', entry.height, util.hex32(entry.version)); @@ -1006,7 +1108,6 @@ Chain.prototype.setBestChain = async function setBestChain(entry, block, prev, f /** * Save block on an alternate chain. - * @method * @private * @param {ChainEntry} entry * @param {Block} block @@ -1032,7 +1133,7 @@ Chain.prototype.saveAlternate = async function saveAlternate(entry, block, prev, } // Warn of unknown versionbits. - if (entry.hasUnknown()) { + if (entry.hasUnknown(this.network)) { this.logger.warning( 'Unknown version bits in block %d: %s.', entry.height, util.hex32(entry.version)); @@ -1062,7 +1163,6 @@ Chain.prototype.saveAlternate = async function saveAlternate(entry, block, prev, * Reset the chain to the desired block. This * is useful for replaying the blockchain download * for SPV. - * @method * @param {Hash|Number} block * @returns {Promise} */ @@ -1078,7 +1178,6 @@ Chain.prototype.reset = async function reset(block) { /** * Reset the chain to the desired block without a lock. - * @method * @private * @param {Hash|Number} block * @returns {Promise} @@ -1112,7 +1211,6 @@ Chain.prototype._reset = async function _reset(block, silent) { /** * Reset the chain to a height or hash. Useful for replaying * the blockchain download for SPV. - * @method * @param {Hash|Number} block - hash/height * @returns {Promise} */ @@ -1128,7 +1226,6 @@ Chain.prototype.replay = async function replay(block) { /** * Reset the chain without a lock. - * @method * @private * @param {Hash|Number} block - hash/height * @param {Boolean?} silent @@ -1136,12 +1233,12 @@ Chain.prototype.replay = async function replay(block) { */ Chain.prototype._replay = async function _replay(block, silent) { - const entry = await this.db.getEntry(block); + const entry = await this.getEntry(block); if (!entry) throw new Error('Block not found.'); - if (!await entry.isMainChain()) + if (!await this.isMainChain(entry)) throw new Error('Cannot reset on alternate chain.'); if (entry.isGenesis()) { @@ -1154,7 +1251,6 @@ Chain.prototype._replay = async function _replay(block, silent) { /** * Invalidate block. - * @method * @param {Hash} hash * @returns {Promise} */ @@ -1170,7 +1266,6 @@ Chain.prototype.invalidate = async function invalidate(hash) { /** * Invalidate block (no lock). - * @method * @param {Hash} hash * @returns {Promise} */ @@ -1182,14 +1277,13 @@ Chain.prototype._invalidate = async function _invalidate(hash) { /** * Retroactively prune the database. - * @method * @returns {Promise} */ Chain.prototype.prune = async function prune() { const unlock = await this.locker.lock(); try { - return await this.db.prune(this.tip.hash); + return await this.db.prune(); } finally { unlock(); } @@ -1197,7 +1291,6 @@ Chain.prototype.prune = async function prune() { /** * Scan the blockchain for transactions containing specified address hashes. - * @method * @param {Hash} start - Block hash to start at. * @param {Bloom} filter - Bloom filter containing tx and address hashes. * @param {Function} iter - Iterator. @@ -1215,7 +1308,6 @@ Chain.prototype.scan = async function scan(start, filter, iter) { /** * Add a block to the chain, perform all necessary verification. - * @method * @param {Block} block * @param {Number?} flags * @param {Number?} id @@ -1234,7 +1326,6 @@ Chain.prototype.add = async function add(block, flags, id) { /** * Add a block to the chain without a lock. - * @method * @private * @param {Block} block * @param {Number?} flags @@ -1283,13 +1374,13 @@ Chain.prototype._add = async function _add(block, flags, id) { } // Do we already have this block? - if (await this.db.hasEntry(hash)) { + if (await this.hasEntry(hash)) { this.logger.debug('Already have block: %s.', block.rhash()); throw new VerifyError(block, 'duplicate', 'duplicate', 0); } // Find the previous block entry. - const prev = await this.db.getEntry(block.prevBlock); + const prev = await this.getEntry(block.prevBlock); // If previous block wasn't ever seen, // add it current to orphans and return. @@ -1310,7 +1401,6 @@ Chain.prototype._add = async function _add(block, flags, id) { /** * Connect block to chain. - * @method * @private * @param {ChainEntry} prev * @param {Block} block @@ -1348,7 +1438,7 @@ Chain.prototype.connect = async function connect(prev, block, flags) { } // Create a new chain entry. - const entry = ChainEntry.fromBlock(this, block, prev); + const entry = ChainEntry.fromBlock(block, prev); // The block is on a alternate chain if the // chainwork is less than or equal to @@ -1373,7 +1463,6 @@ Chain.prototype.connect = async function connect(prev, block, flags) { /** * Handle orphans. - * @method * @private * @param {ChainEntry} entry * @returns {Promise} @@ -1700,7 +1789,6 @@ Chain.prototype.removeInvalid = function removeInvalid(hash) { /** * Test the chain to see if it contains * a block, or has recently seen a block. - * @method * @param {Hash} hash * @returns {Promise} - Returns Boolean. */ @@ -1728,6 +1816,47 @@ Chain.prototype.getEntry = function getEntry(hash) { return this.db.getEntry(hash); }; +/** + * Retrieve a chain entry by height. + * @param {Number} height + * @returns {Promise} - Returns {@link ChainEntry}. + */ + +Chain.prototype.getEntryByHeight = function getEntryByHeight(height) { + return this.db.getEntryByHeight(height); +}; + +/** + * Retrieve a chain entry by hash. + * @param {Hash} hash + * @returns {Promise} - Returns {@link ChainEntry}. + */ + +Chain.prototype.getEntryByHash = function getEntryByHash(hash) { + return this.db.getEntryByHash(hash); +}; + +/** + * Get the hash of a block by height. Note that this + * will only return hashes in the main chain. + * @param {Number} height + * @returns {Promise} - Returns {@link Hash}. + */ + +Chain.prototype.getHash = function getHash(height) { + return this.db.getHash(height); +}; + +/** + * Get the height of a block by hash. + * @param {Hash} hash + * @returns {Promise} - Returns Number. + */ + +Chain.prototype.getHeight = function getHeight(hash) { + return this.db.getHeight(hash); +}; + /** * Test the chain to see if it contains a block. * @param {Hash} hash @@ -1738,6 +1867,157 @@ Chain.prototype.hasEntry = function hasEntry(hash) { return this.db.hasEntry(hash); }; +/** + * Get the _next_ block hash (does not work by height). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Hash}. + */ + +Chain.prototype.getNextHash = function getNextHash(hash) { + return this.db.getNextHash(hash); +}; + +/** + * Check whether coins are still unspent. Necessary for bip30. + * @see https://bitcointalk.org/index.php?topic=67738.0 + * @param {TX} tx + * @returns {Promise} - Returns Boolean. + */ + +Chain.prototype.hasCoins = function hasCoins(tx) { + return this.db.hasCoins(tx); +}; + +/** + * Get all tip hashes. + * @returns {Promise} - Returns {@link Hash}[]. + */ + +Chain.prototype.getTips = function getTips() { + return this.db.getTips(); +}; + +/** + * Get a coin (unspents only). + * @private + * @param {Outpoint} prevout + * @returns {Promise} - Returns {@link CoinEntry}. + */ + +Chain.prototype.readCoin = function readCoin(prevout) { + return this.db.readCoin(prevout); +}; + +/** + * Get a coin (unspents only). + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ + +Chain.prototype.getCoin = function getCoin(hash, index) { + return this.db.getCoin(hash, index); +}; + +/** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ + +Chain.prototype.getBlock = function getBlock(hash) { + return this.db.getBlock(hash); +}; + +/** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ + +Chain.prototype.getRawBlock = function getRawBlock(block) { + return this.db.getRawBlock(block); +}; + +/** + * Get a historical block coin viewpoint. + * @param {Block} hash + * @returns {Promise} - Returns {@link CoinView}. + */ + +Chain.prototype.getBlockView = function getBlockView(block) { + return this.db.getBlockView(block); +}; + +/** + * Get a transaction with metadata. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TXMeta}. + */ + +Chain.prototype.getMeta = function getMeta(hash) { + return this.db.getMeta(hash); +}; + +/** + * Retrieve a transaction. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ + +Chain.prototype.getTX = function getTX(hash) { + return this.db.getTX(hash); +}; + +/** + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ + +Chain.prototype.hasTX = function hasTX(hash) { + return this.db.hasTX(hash); +}; + +/** + * Get all coins pertinent to an address. + * @param {Address[]} addrs + * @returns {Promise} - Returns {@link Coin}[]. + */ + +Chain.prototype.getCoinsByAddress = function getCoinsByAddress(addrs) { + return this.db.getCoinsByAddress(addrs); +}; + +/** + * Get all transaction hashes to an address. + * @param {Address[]} addrs + * @returns {Promise} - Returns {@link Hash}[]. + */ + +Chain.prototype.getHashesByAddress = function getHashesByAddress(addrs) { + return this.db.getHashesByAddress(addrs); +}; + +/** + * Get all transactions pertinent to an address. + * @param {Address[]} addrs + * @returns {Promise} - Returns {@link TX}[]. + */ + +Chain.prototype.getTXByAddress = function getTXByAddress(addrs) { + return this.db.getTXByAddress(addrs); +}; + +/** + * Get all transactions pertinent to an address. + * @param {Address[]} addrs + * @returns {Promise} - Returns {@link TXMeta}[]. + */ + +Chain.prototype.getMetaByAddress = function getMetaByAddress(addrs) { + return this.db.getMetaByAddress(addrs); +}; + /** * Get an orphan block. * @param {Hash} hash @@ -1768,9 +2048,18 @@ Chain.prototype.hasPending = function hasPending(hash) { return this.locker.hasPending(hash); }; +/** + * Get coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ + +Chain.prototype.getCoinView = function getCoinView(tx) { + return this.db.getCoinView(tx); +}; + /** * Get coin viewpoint (spent). - * @method * @param {TX} tx * @returns {Promise} - Returns {@link CoinView}. */ @@ -1842,7 +2131,6 @@ Chain.prototype.getProgress = function getProgress() { /** * Calculate chain locator (an array of hashes). - * @method * @param {Hash?} start - Height or hash to treat as the tip. * The current tip will be used if not present. Note that this can be a * non-existent hash, which is useful for headers-first locators. @@ -1860,7 +2148,6 @@ Chain.prototype.getLocator = async function getLocator(start) { /** * Calculate chain locator without a lock. - * @method * @private * @param {Hash?} start * @returns {Promise} @@ -1872,7 +2159,8 @@ Chain.prototype._getLocator = async function _getLocator(start) { assert(typeof start === 'string'); - let entry = await this.db.getEntry(start); + let entry = await this.getEntry(start); + const hashes = []; if (!entry) { @@ -1882,9 +2170,10 @@ Chain.prototype._getLocator = async function _getLocator(start) { let hash = entry.hash; let height = entry.height; - const main = await entry.isMainChain(); let step = 1; + const main = await this.isMainChain(entry); + hashes.push(hash); while (height > 0) { @@ -1899,10 +2188,10 @@ Chain.prototype._getLocator = async function _getLocator(start) { if (main) { // If we're on the main chain, we can // do a fast lookup of the hash. - hash = await this.db.getHash(height); + hash = await this.getHash(height); assert(hash); } else { - const ancestor = await entry.getAncestor(height); + const ancestor = await this.getAncestor(entry, height); assert(ancestor); hash = ancestor.hash; } @@ -1968,7 +2257,6 @@ Chain.prototype.getProofTime = function getProofTime(to, from) { /** * Calculate the next target based on the chain tip. - * @method * @returns {Promise} - returns Number * (target is in compact/mantissa form). */ @@ -1979,7 +2267,6 @@ Chain.prototype.getCurrentTarget = async function getCurrentTarget() { /** * Calculate the next target. - * @method * @param {Number} time - Next block timestamp. * @param {ChainEntry} prev - Previous entry. * @returns {Promise} - returns Number @@ -2005,14 +2292,14 @@ Chain.prototype.getTarget = async function getTarget(time, prev) { while (prev.height !== 0 && prev.height % pow.retargetInterval !== 0 && prev.bits === pow.bits) { - const cache = prev.getPrevCache(); + const cache = this.getPrevCache(prev); if (cache) { prev = cache; continue; } - prev = await prev.getPrevious(); + prev = await this.getPrevious(prev); assert(prev); } } @@ -2023,7 +2310,7 @@ Chain.prototype.getTarget = async function getTarget(time, prev) { const height = prev.height - (pow.retargetInterval - 1); assert(height >= 0); - const first = await prev.getAncestor(height); + const first = await this.getAncestor(prev, height); assert(first); return this.retarget(prev, first); @@ -2065,7 +2352,6 @@ Chain.prototype.retarget = function retarget(prev, first) { /** * Find a locator. Analagous to bitcoind's `FindForkInGlobalIndex()`. - * @method * @param {Hash[]} locator - Hashes. * @returns {Promise} - Returns {@link Hash} (the * hash of the latest known block). @@ -2073,7 +2359,7 @@ Chain.prototype.retarget = function retarget(prev, first) { Chain.prototype.findLocator = async function findLocator(locator) { for (const hash of locator) { - if (await this.db.isMainChain(hash)) + if (await this.isMainHash(hash)) return hash; } @@ -2084,7 +2370,6 @@ Chain.prototype.findLocator = async function findLocator(locator) { * Check whether a versionbits deployment is active (BIP9: versionbits). * @example * await chain.isActive(tip, deployments.segwit); - * @method * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki * @param {ChainEntry} prev - Previous chain entry. * @param {String} id - Deployment id. @@ -2098,7 +2383,6 @@ Chain.prototype.isActive = async function isActive(prev, deployment) { /** * Get chain entry state for a deployment (BIP9: versionbits). - * @method * @example * await chain.getState(tip, deployments.segwit); * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki @@ -2120,7 +2404,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { if (((prev.height + 1) % window) !== 0) { const height = prev.height - ((prev.height + 1) % window); - prev = await prev.getAncestor(height); + prev = await this.getAncestor(prev, height); if (!prev) return thresholdStates.DEFINED; @@ -2141,7 +2425,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { break; } - const time = await entry.getMedianTime(); + const time = await this.getMedianTime(entry); if (time < deployment.startTime) { state = thresholdStates.DEFINED; @@ -2152,7 +2436,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { compute.push(entry); const height = entry.height - window; - entry = await entry.getAncestor(height); + entry = await this.getAncestor(entry, height); } while (compute.length) { @@ -2160,7 +2444,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { switch (state) { case thresholdStates.DEFINED: { - const time = await entry.getMedianTime(); + const time = await this.getMedianTime(entry); if (time >= deployment.timeout) { state = thresholdStates.FAILED; @@ -2175,7 +2459,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { break; } case thresholdStates.STARTED: { - const time = await entry.getMedianTime(); + const time = await this.getMedianTime(entry); let block = entry; let count = 0; @@ -2193,7 +2477,7 @@ Chain.prototype.getState = async function getState(prev, deployment) { break; } - block = await block.getPrevious(); + block = await this.getPrevious(block); assert(block); } @@ -2221,7 +2505,6 @@ Chain.prototype.getState = async function getState(prev, deployment) { /** * Compute the version for a new block (BIP9: versionbits). - * @method * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki * @param {ChainEntry} prev - Previous chain entry (usually the tip). * @returns {Promise} - Returns Number. @@ -2247,13 +2530,12 @@ Chain.prototype.computeBlockVersion = async function computeBlockVersion(prev) { /** * Get the current deployment state of the chain. Called on load. - * @method * @private * @returns {Promise} - Returns {@link DeploymentState}. */ Chain.prototype.getDeploymentState = async function getDeploymentState() { - const prev = await this.tip.getPrevious(); + const prev = await this.getPrevious(this.tip); if (!prev) { assert(this.tip.isGenesis()); @@ -2269,7 +2551,6 @@ Chain.prototype.getDeploymentState = async function getDeploymentState() { /** * Check transaction finality, taking into account MEDIAN_TIME_PAST * if it is present in the lock flags. - * @method * @param {ChainEntry} prev - Previous chain entry. * @param {TX} tx * @param {LockFlags} flags @@ -2284,7 +2565,7 @@ Chain.prototype.verifyFinal = async function verifyFinal(prev, tx, flags) { return tx.isFinal(height, -1); if (flags & common.lockFlags.MEDIAN_TIME_PAST) { - const time = await prev.getMedianTime(); + const time = await this.getMedianTime(prev); return tx.isFinal(height, time); } @@ -2293,7 +2574,6 @@ Chain.prototype.verifyFinal = async function verifyFinal(prev, tx, flags) { /** * Get the necessary minimum time and height sequence locks for a transaction. - * @method * @param {ChainEntry} prev * @param {TX} tx * @param {CoinView} view @@ -2333,10 +2613,10 @@ Chain.prototype.getLocks = async function getLocks(prev, tx, view, flags) { height = Math.max(height - 1, 0); - const entry = await prev.getAncestor(height); + const entry = await this.getAncestor(prev, height); assert(entry, 'Database is corrupt.'); - let time = await entry.getMedianTime(); + let time = await this.getMedianTime(entry); time += ((sequence & MASK) << GRANULARITY) - 1; minTime = Math.max(minTime, time); } @@ -2346,7 +2626,6 @@ Chain.prototype.getLocks = async function getLocks(prev, tx, view, flags) { /** * Verify sequence locks. - * @method * @param {ChainEntry} prev * @param {TX} tx * @param {CoinView} view @@ -2365,7 +2644,7 @@ Chain.prototype.verifyLocks = async function verifyLocks(prev, tx, view, flags) if (time === -1) return true; - const mtp = await prev.getMedianTime(); + const mtp = await this.getMedianTime(prev); if (time >= mtp) return false; @@ -2650,6 +2929,14 @@ function Orphan(block, flags, id) { this.time = util.now(); } +/* + * Helpers + */ + +function cmp(a, b) { + return a - b; +} + /* * Expose */ diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index c1c184df8..9761d3cfa 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -33,7 +33,6 @@ const U32 = encoding.U32; * The database backend for the {@link Chain} object. * @alias module:blockchain.ChainDB * @constructor - * @param {Chain} chain * @param {Boolean?} options.prune - Whether to prune the chain. * @param {Boolean?} options.spv - SPV-mode, will not save block * data, only entries. @@ -45,12 +44,11 @@ const U32 = encoding.U32; * @emits ChainDB#error */ -function ChainDB(chain) { +function ChainDB(options) { if (!(this instanceof ChainDB)) - return new ChainDB(chain); + return new ChainDB(options); - this.chain = chain; - this.options = chain.options; + this.options = options; this.network = this.options.network; this.logger = this.options.logger.context('chaindb'); @@ -74,7 +72,6 @@ ChainDB.layout = layout; /** * Open the chain db, wait for the database to load. - * @method * @returns {Promise} */ @@ -201,7 +198,6 @@ ChainDB.prototype.drop = function drop() { /** * Commit current batch. - * @method * @returns {Promise} */ @@ -267,7 +263,6 @@ ChainDB.prototype.getCache = function getCache(block) { /** * Get the height of a block by hash. - * @method * @param {Hash} hash * @returns {Promise} - Returns Number. */ @@ -297,7 +292,6 @@ ChainDB.prototype.getHeight = async function getHeight(hash) { /** * Get the hash of a block by height. Note that this * will only return hashes in the main chain. - * @method * @param {Number} height * @returns {Promise} - Returns {@link Hash}. */ @@ -326,7 +320,6 @@ ChainDB.prototype.getHash = async function getHash(height) { /** * Retrieve a chain entry by height. - * @method * @param {Number} height * @returns {Promise} - Returns {@link ChainEntry}. */ @@ -349,7 +342,7 @@ ChainDB.prototype.getEntryByHeight = async function getEntryByHeight(height) { const hash = data.toString('hex'); - const state = this.chain.state; + const state = this.state; const entry = await this.getEntryByHash(hash); if (!entry) @@ -358,7 +351,7 @@ ChainDB.prototype.getEntryByHeight = async function getEntryByHeight(height) { // By the time getEntry has completed, // a reorg may have occurred. This entry // may not be on the main chain anymore. - if (this.chain.state === state) + if (this.state === state) this.cacheHeight.set(entry.height, entry); return entry; @@ -366,7 +359,6 @@ ChainDB.prototype.getEntryByHeight = async function getEntryByHeight(height) { /** * Retrieve a chain entry by hash. - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link ChainEntry}. */ @@ -387,7 +379,7 @@ ChainDB.prototype.getEntryByHash = async function getEntryByHash(hash) { if (!raw) return null; - const entry = ChainEntry.fromRaw(this.chain, raw); + const entry = ChainEntry.fromRaw(raw); // There's no efficient way to check whether // this is in the main chain or not, so @@ -411,7 +403,6 @@ ChainDB.prototype.getEntry = function getEntry(block) { /** * Test whether the chain contains a block. - * @method * @param {Hash} hash * @returns {Promise} - Returns Boolean. */ @@ -421,18 +412,102 @@ ChainDB.prototype.hasEntry = async function hasEntry(hash) { return height !== -1; }; +/** + * Get ancestor by `height`. + * @param {ChainEntry} entry + * @param {Number} height + * @returns {Promise} - Returns ChainEntry. + */ + +ChainDB.prototype.getAncestor = async function getAncestor(entry, height) { + if (height < 0) + return null; + + assert(height >= 0); + assert(height <= entry.height); + + if (await this.isMainChain(entry)) + return await this.getEntryByHeight(height); + + while (entry.height !== height) { + const cache = this.getPrevCache(entry); + + if (cache) + entry = cache; + else + entry = await this.getPrevious(entry); + + assert(entry); + } + + return entry; +}; + +/** + * Get previous entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ + +ChainDB.prototype.getPrevious = function getPrevious(entry) { + return this.getEntryByHash(entry.prevBlock); +}; + +/** + * Get previous cached entry. + * @param {ChainEntry} entry + * @returns {ChainEntry|null} + */ + +ChainDB.prototype.getPrevCache = function getPrevCache(entry) { + return this.cacheHash.get(entry.prevBlock) || null; +}; + +/** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ + +ChainDB.prototype.getNext = async function getNext(entry) { + const hash = await this.getNextHash(entry.hash); + + if (!hash) + return null; + + return await this.getEntryByHash(hash); +}; + +/** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ + +ChainDB.prototype.getNextEntry = async function getNextEntry(entry) { + const next = await this.getEntryByHeight(entry.height + 1); + + if (!next) + return null; + + // Not on main chain. + if (next.prevBlock !== entry.hash) + return null; + + return next; +}; + /** * Retrieve the tip entry from the tip record. * @returns {Promise} - Returns {@link ChainEntry}. */ ChainDB.prototype.getTip = function getTip() { - return this.getEntry(this.state.hash()); + return this.getEntryByHash(this.state.tip); }; /** * Retrieve the tip entry from the tip record. - * @method * @returns {Promise} - Returns {@link ChainState}. */ @@ -447,14 +522,13 @@ ChainDB.prototype.getState = async function getState() { /** * Write genesis block to database. - * @method * @returns {Promise} */ ChainDB.prototype.saveGenesis = async function saveGenesis() { const genesis = this.network.genesisBlock; const block = Block.fromRaw(genesis, 'hex'); - const entry = ChainEntry.fromBlock(this.chain, block); + const entry = ChainEntry.fromBlock(block); this.logger.info('Writing genesis block to ChainDB.'); @@ -463,7 +537,6 @@ ChainDB.prototype.saveGenesis = async function saveGenesis() { /** * Retrieve the database flags. - * @method * @returns {Promise} - Returns {@link ChainFlags}. */ @@ -478,7 +551,6 @@ ChainDB.prototype.getFlags = async function getFlags() { /** * Verify current options against db options. - * @method * @param {ChainState} state * @returns {Promise} */ @@ -547,13 +619,12 @@ ChainDB.prototype.verifyFlags = async function verifyFlags(state) { if (needsPrune) { await this.logger.info('Retroactively pruning chain.'); - await this.prune(state.hash()); + await this.prune(state.tip); } }; /** * Get state caches. - * @method * @returns {Promise} - Returns {@link StateCache}. */ @@ -609,7 +680,6 @@ ChainDB.prototype.writeDeployments = function writeDeployments(batch) { /** * Check for outdated deployments. - * @method * @private * @returns {Promise} */ @@ -647,7 +717,6 @@ ChainDB.prototype.checkDeployments = async function checkDeployments() { /** * Potentially invalidate state cache. - * @method * @returns {Promise} */ @@ -684,7 +753,6 @@ ChainDB.prototype.verifyDeployments = async function verifyDeployments() { /** * Invalidate state cache. - * @method * @private * @returns {Promise} */ @@ -701,12 +769,10 @@ ChainDB.prototype.invalidateCache = async function invalidateCache(bit, batch) { /** * Retroactively prune the database. - * @method - * @param {Hash} tip * @returns {Promise} */ -ChainDB.prototype.prune = async function prune(tip) { +ChainDB.prototype.prune = async function prune() { const options = this.options; const keepBlocks = this.network.block.keepBlocks; const pruneAfter = this.network.block.pruneAfterHeight; @@ -716,7 +782,7 @@ ChainDB.prototype.prune = async function prune(tip) { if (flags.prune) throw new Error('Chain is already pruned.'); - const height = await this.getHeight(tip); + const height = await this.getHeight(this.state.tip); if (height <= pruneAfter + keepBlocks) return false; @@ -756,7 +822,6 @@ ChainDB.prototype.prune = async function prune(tip) { /** * Get the _next_ block hash (does not work by height). - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link Hash}. */ @@ -772,22 +837,22 @@ ChainDB.prototype.getNextHash = async function getNextHash(hash) { /** * Check to see if a block is on the main chain. - * @method - * @param {ChainEntry|Hash} hash + * @param {Hash} hash * @returns {Promise} - Returns Boolean. */ -ChainDB.prototype.isMainChain = async function isMainChain(hash) { +ChainDB.prototype.isMainHash = async function isMainHash(hash) { assert(typeof hash === 'string'); - if (hash === this.chain.tip.hash - || hash === this.network.genesis.hash) { - return true; - } - if (hash === encoding.NULL_HASH) return false; + if (hash === this.network.genesis.hash) + return true; + + if (hash === this.state.tip) + return true; + const cacheHash = this.cacheHash.get(hash); if (cacheHash) { @@ -802,6 +867,30 @@ ChainDB.prototype.isMainChain = async function isMainChain(hash) { return false; }; +/** + * Test whether the entry is in the main chain. + * @param {ChainEntry} entry + * @returns {Promise} - Returns Boolean. + */ + +ChainDB.prototype.isMainChain = async function isMainChain(entry) { + if (entry.isGenesis()) + return true; + + if (entry.hash === this.state.tip) + return true; + + const cache = this.getCache(entry.height); + + if (cache) + return entry.hash === cache.hash; + + if (await this.getNextHash(entry.hash)) + return true; + + return false; +}; + /** * Get all entries. * @returns {Promise} - Returns {@link ChainEntry}[]. @@ -811,7 +900,7 @@ ChainDB.prototype.getEntries = function getEntries() { return this.db.values({ gte: layout.e(encoding.ZERO_HASH), lte: layout.e(encoding.MAX_HASH), - parse: value => ChainEntry.fromRaw(this.chain, value) + parse: value => ChainEntry.fromRaw(value) }); }; @@ -830,7 +919,6 @@ ChainDB.prototype.getTips = function getTips() { /** * Get a coin (unspents only). - * @method * @private * @param {Outpoint} prevout * @returns {Promise} - Returns {@link CoinEntry}. @@ -862,7 +950,6 @@ ChainDB.prototype.readCoin = async function readCoin(prevout) { /** * Get a coin (unspents only). - * @method * @param {Hash} hash * @param {Number} index * @returns {Promise} - Returns {@link Coin}. @@ -896,7 +983,6 @@ ChainDB.prototype.hasCoins = async function hasCoins(tx) { /** * Get coin viewpoint. - * @method * @param {TX} tx * @returns {Promise} - Returns {@link CoinView}. */ @@ -921,7 +1007,6 @@ ChainDB.prototype.getCoinView = async function getCoinView(tx) { /** * Get coin viewpoint (historical). - * @method * @param {TX} tx * @returns {Promise} - Returns {@link CoinView}. */ @@ -946,7 +1031,6 @@ ChainDB.prototype.getSpentView = async function getSpentView(tx) { /** * Get coins necessary to be resurrected during a reorg. - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link Coin}[]. */ @@ -962,7 +1046,6 @@ ChainDB.prototype.getUndoCoins = async function getUndoCoins(hash) { /** * Retrieve a block from the database (not filled with coins). - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link Block}. */ @@ -978,7 +1061,6 @@ ChainDB.prototype.getBlock = async function getBlock(hash) { /** * Retrieve a block from the database (not filled with coins). - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link Block}. */ @@ -997,7 +1079,6 @@ ChainDB.prototype.getRawBlock = async function getRawBlock(block) { /** * Get a historical block coin viewpoint. - * @method * @param {Block} hash * @returns {Promise} - Returns {@link CoinView}. */ @@ -1026,7 +1107,6 @@ ChainDB.prototype.getBlockView = async function getBlockView(block) { /** * Get a transaction with metadata. - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link TXMeta}. */ @@ -1045,7 +1125,6 @@ ChainDB.prototype.getMeta = async function getMeta(hash) { /** * Retrieve a transaction. - * @method * @param {Hash} hash * @returns {Promise} - Returns {@link TX}. */ @@ -1073,7 +1152,6 @@ ChainDB.prototype.hasTX = async function hasTX(hash) { /** * Get all coins pertinent to an address. - * @method * @param {Address[]} addrs * @returns {Promise} - Returns {@link Coin}[]. */ @@ -1108,7 +1186,6 @@ ChainDB.prototype.getCoinsByAddress = async function getCoinsByAddress(addrs) { /** * Get all transaction hashes to an address. - * @method * @param {Address[]} addrs * @returns {Promise} - Returns {@link Hash}[]. */ @@ -1137,7 +1214,6 @@ ChainDB.prototype.getHashesByAddress = async function getHashesByAddress(addrs) /** * Get all transactions pertinent to an address. - * @method * @param {Address[]} addrs * @returns {Promise} - Returns {@link TX}[]. */ @@ -1154,7 +1230,6 @@ ChainDB.prototype.getTXByAddress = async function getTXByAddress(addrs) { /** * Get all transactions pertinent to an address. - * @method * @param {Address[]} addrs * @returns {Promise} - Returns {@link TXMeta}[]. */ @@ -1180,7 +1255,6 @@ ChainDB.prototype.getMetaByAddress = async function getMetaByAddress(addrs) { /** * Scan the blockchain for transactions containing specified address hashes. - * @method * @param {Hash} start - Block hash to start at. * @param {Bloom} filter - Bloom filter containing tx and address hashes. * @param {Function} iter - Iterator. @@ -1201,7 +1275,7 @@ ChainDB.prototype.scan = async function scan(start, filter, iter) { if (!entry) return; - if (!await entry.isMainChain()) + if (!await this.isMainChain(entry)) throw new Error('Cannot rescan an alternate chain.'); let total = 0; @@ -1216,7 +1290,7 @@ ChainDB.prototype.scan = async function scan(start, filter, iter) { if (!this.options.spv && !this.options.prune) throw new Error('Block not found.'); await iter(entry, txs); - entry = await entry.getNext(); + entry = await this.getNext(entry); continue; } @@ -1260,7 +1334,7 @@ ChainDB.prototype.scan = async function scan(start, filter, iter) { await iter(entry, txs); - entry = await entry.getNext(); + entry = await this.getNext(entry); } this.logger.info('Finished scanning %d blocks.', total); @@ -1271,7 +1345,6 @@ ChainDB.prototype.scan = async function scan(start, filter, iter) { * connect it as the tip. Note that this method * does _not_ perform any verification which is * instead performed in {@link Chain#add}. - * @method * @param {ChainEntry} entry * @param {Block} block * @param {CoinView?} view - Will not connect if null. @@ -1291,7 +1364,6 @@ ChainDB.prototype.save = async function save(entry, block, view) { /** * Save an entry without a batch. - * @method * @private * @param {ChainEntry} entry * @param {Block} block @@ -1339,7 +1411,6 @@ ChainDB.prototype._save = async function _save(entry, block, view) { /** * Reconnect the block to the chain. - * @method * @param {ChainEntry} entry * @param {Block} block * @param {CoinView} view @@ -1359,7 +1430,6 @@ ChainDB.prototype.reconnect = async function reconnect(entry, block, view) { /** * Reconnect block without a batch. - * @method * @private * @param {ChainEntry} entry * @param {Block} block @@ -1394,7 +1464,6 @@ ChainDB.prototype._reconnect = async function _reconnect(entry, block, view) { /** * Disconnect block from the chain. - * @method * @param {ChainEntry} entry * @param {Block} block * @returns {Promise} @@ -1419,7 +1488,6 @@ ChainDB.prototype.disconnect = async function disconnect(entry, block) { /** * Disconnect block without a batch. * @private - * @method * @param {ChainEntry} entry * @param {Block} block * @returns {Promise} - Returns {@link CoinView}. @@ -1467,7 +1535,6 @@ ChainDB.prototype.saveUpdates = function saveUpdates() { /** * Reset the chain to a height or hash. Useful for replaying * the blockchain download for SPV. - * @method * @param {Hash|Number} block - hash/height * @returns {Promise} */ @@ -1478,7 +1545,7 @@ ChainDB.prototype.reset = async function reset(block) { if (!entry) throw new Error('Block not found.'); - if (!await entry.isMainChain()) + if (!await this.isMainChain(entry)) throw new Error('Cannot reset on alternate chain.'); if (this.options.prune) @@ -1535,7 +1602,7 @@ ChainDB.prototype.reset = async function reset(block) { this.cacheHeight.remove(tip.height); this.cacheHash.remove(tip.hash); - tip = await this.getEntry(tip.prevBlock); + tip = await this.getPrevious(tip); assert(tip); } @@ -1544,7 +1611,6 @@ ChainDB.prototype.reset = async function reset(block) { /** * Remove all alternate chains. - * @method * @returns {Promise} */ @@ -1568,14 +1634,13 @@ ChainDB.prototype.removeChains = async function removeChains() { /** * Remove an alternate chain. - * @method * @private * @param {Hash} hash - Alternate chain tip. * @returns {Promise} */ ChainDB.prototype._removeChain = async function _removeChain(hash) { - let tip = await this.getEntry(hash); + let tip = await this.getEntryByHash(hash); if (!tip) throw new Error('Alternate chain tip not found.'); @@ -1583,7 +1648,7 @@ ChainDB.prototype._removeChain = async function _removeChain(hash) { this.logger.debug('Removing alternate chain: %s.', tip.rhash()); for (;;) { - if (await tip.isMainChain()) + if (await this.isMainChain(tip)) break; assert(!tip.isGenesis()); @@ -1598,7 +1663,7 @@ ChainDB.prototype._removeChain = async function _removeChain(hash) { // on successful write. this.cacheHash.unpush(tip.hash); - tip = await this.getEntry(tip.prevBlock); + tip = await this.getPrevious(tip); assert(tip); } }; @@ -1606,7 +1671,6 @@ ChainDB.prototype._removeChain = async function _removeChain(hash) { /** * Save a block (not an entry) to the * database and potentially connect the inputs. - * @method * @param {ChainEntry} entry * @param {Block} block * @param {CoinView?} view @@ -1632,7 +1696,6 @@ ChainDB.prototype.saveBlock = async function saveBlock(entry, block, view) { /** * Remove a block (not an entry) to the database. * Disconnect inputs. - * @method * @param {ChainEntry} entry * @returns {Promise} - Returns {@link Block}. */ @@ -1676,7 +1739,6 @@ ChainDB.prototype.saveView = function saveView(view) { /** * Connect block inputs. - * @method * @param {ChainEntry} entry * @param {Block} block * @param {CoinView} view @@ -1692,7 +1754,7 @@ ChainDB.prototype.connectBlock = async function connectBlock(entry, block, view) this.pending.connect(block); // Genesis block's coinbase is unspendable. - if (this.chain.isGenesis(block)) + if (entry.isGenesis()) return; // Update chain state value. @@ -1728,7 +1790,6 @@ ChainDB.prototype.connectBlock = async function connectBlock(entry, block, view) /** * Disconnect block inputs. - * @method * @param {ChainEntry} entry * @param {Block} block * @returns {Promise} - Returns {@link CoinView}. @@ -1788,7 +1849,6 @@ ChainDB.prototype.disconnectBlock = async function disconnectBlock(entry, block) /** * Prune a block from the chain and * add current block to the prune queue. - * @method * @private * @param {ChainEntry} entry * @returns {Promise} @@ -2042,19 +2102,15 @@ ChainFlags.fromRaw = function fromRaw(data) { */ function ChainState() { - this.tip = encoding.ZERO_HASH; + this.tip = encoding.NULL_HASH; this.tx = 0; this.coin = 0; this.value = 0; this.committed = false; } -ChainState.prototype.hash = function hash() { - return this.tip.toString('hex'); -}; - ChainState.prototype.rhash = function rhash() { - return util.revHex(this.hash()); + return util.revHex(this.tip); }; ChainState.prototype.clone = function clone() { @@ -2085,8 +2141,8 @@ ChainState.prototype.spend = function spend(coin) { }; ChainState.prototype.commit = function commit(hash) { - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); + if (typeof hash !== 'string') + hash = hash.toString('hex'); this.tip = hash; this.committed = true; return this.toRaw(); @@ -2104,7 +2160,7 @@ ChainState.prototype.toRaw = function toRaw() { ChainState.fromRaw = function fromRaw(data) { const state = new ChainState(); const br = new BufferReader(data); - state.tip = br.readHash(); + state.tip = br.readHash('hex'); state.tx = br.readU64(); state.coin = br.readU64(); state.value = br.readU64(); diff --git a/lib/blockchain/chainentry.js b/lib/blockchain/chainentry.js index 43b549867..de9fece00 100644 --- a/lib/blockchain/chainentry.js +++ b/lib/blockchain/chainentry.js @@ -27,9 +27,7 @@ const ZERO = new BN(0); * boot and recalculating the chainworks. * @alias module:blockchain.ChainEntry * @constructor - * @param {Chain} chain - * @param {Object} options - * @param {ChainEntry} prev + * @param {Object?} options * @property {Hash} hash * @property {Number} version - Transaction version. Note that Bcoin reads * versions as unsigned even though they are signed at the protocol level. @@ -44,11 +42,10 @@ const ZERO = new BN(0); * @property {ReversedHash} rhash - Reversed block hash (uint256le). */ -function ChainEntry(chain, options, prev) { +function ChainEntry(options) { if (!(this instanceof ChainEntry)) - return new ChainEntry(chain, options, prev); + return new ChainEntry(options); - this.chain = chain; this.hash = encoding.NULL_HASH; this.version = 1; this.prevBlock = encoding.NULL_HASH; @@ -60,7 +57,7 @@ function ChainEntry(chain, options, prev) { this.chainwork = ZERO; if (options) - this.fromOptions(options, prev); + this.fromOptions(options); } /** @@ -70,22 +67,13 @@ function ChainEntry(chain, options, prev) { ChainEntry.MAX_CHAINWORK = new BN(1).ushln(256); -/** - * Size of set to pick median time from. - * @const {Number} - * @default - */ - -ChainEntry.MEDIAN_TIMESPAN = 11; - /** * Inject properties from options. * @private * @param {Object} options - * @param {ChainEntry} prev - Previous entry. */ -ChainEntry.prototype.fromOptions = function fromOptions(options, prev) { +ChainEntry.prototype.fromOptions = function fromOptions(options) { assert(options, 'Block data is required.'); assert(typeof options.hash === 'string'); assert(util.isU32(options.version)); @@ -107,22 +95,18 @@ ChainEntry.prototype.fromOptions = function fromOptions(options, prev) { this.height = options.height; this.chainwork = options.chainwork || ZERO; - if (!this.chainwork) - this.chainwork = this.getChainwork(prev); - return this; }; /** * Instantiate chainentry from options. - * @param {Chain} chain * @param {Object} options * @param {ChainEntry} prev - Previous entry. * @returns {ChainEntry} */ -ChainEntry.fromOptions = function fromOptions(chain, options, prev) { - return new ChainEntry(chain).fromOptions(options, prev); +ChainEntry.fromOptions = function fromOptions(options, prev) { + return new ChainEntry().fromOptions(options, prev); }; /** @@ -160,178 +144,23 @@ ChainEntry.prototype.getChainwork = function getChainwork(prev) { */ ChainEntry.prototype.isGenesis = function isGenesis() { - return this.hash === this.chain.network.genesis.hash; -}; - -/** - * Test whether the entry is in the main chain. - * @method - * @returns {Promise} - Return Boolean. - */ - -ChainEntry.prototype.isMainChain = async function isMainChain() { - if (this.hash === this.chain.tip.hash - || this.hash === this.chain.network.genesis.hash) { - return true; - } - - const entry = this.chain.db.getCache(this.height); - - if (entry) { - if (entry.hash === this.hash) - return true; - return false; - } - - if (await this.chain.db.getNextHash(this.hash)) - return true; - - return false; -}; - -/** - * Get ancestor by `height`. - * @method - * @param {Number} height - * @returns {Promise} - Returns ChainEntry[]. - */ - -ChainEntry.prototype.getAncestor = async function getAncestor(height) { - if (height < 0) - return null; - - assert(height >= 0); - assert(height <= this.height); - - if (await this.isMainChain()) - return await this.chain.db.getEntry(height); - - let entry = this; - while (entry.height !== height) { - entry = await entry.getPrevious(); - assert(entry); - } - - return entry; -}; - -/** - * Get previous entry. - * @returns {Promise} - Returns ChainEntry. - */ - -ChainEntry.prototype.getPrevious = function getPrevious() { - return this.chain.db.getEntry(this.prevBlock); -}; - -/** - * Get previous cached entry. - * @returns {ChainEntry|null} - */ - -ChainEntry.prototype.getPrevCache = function getPrevCache() { - return this.chain.db.getCache(this.prevBlock); -}; - -/** - * Get next entry. - * @method - * @returns {Promise} - Returns ChainEntry. - */ - -ChainEntry.prototype.getNext = async function getNext() { - const hash = await this.chain.db.getNextHash(this.hash); - - if (!hash) - return null; - - return await this.chain.db.getEntry(hash); -}; - -/** - * Get next entry. - * @method - * @returns {Promise} - Returns ChainEntry. - */ - -ChainEntry.prototype.getNextEntry = async function getNextEntry() { - const entry = await this.chain.db.getEntry(this.height + 1); - - if (!entry) - return null; - - // Not on main chain. - if (entry.prevBlock !== this.hash) - return null; - - return entry; -}; - -/** - * Calculate median time past. - * @method - * @param {Number?} time - * @returns {Promise} - Returns Number. - */ - -ChainEntry.prototype.getMedianTime = async function getMedianTime(time) { - let timespan = ChainEntry.MEDIAN_TIMESPAN; - const median = []; - - // In case we ever want to check - // the MTP of the _current_ block - // (necessary for BIP148). - if (time != null) { - median.push(time); - timespan -= 1; - } - - let entry = this; - for (let i = 0; i < timespan && entry; i++) { - median.push(entry.time); - - const cache = entry.getPrevCache(); - - if (cache) { - entry = cache; - continue; - } - - entry = await entry.getPrevious(); - } - - median.sort(cmp); - - return median[median.length >>> 1]; -}; - -/** - * Test whether the entry is potentially - * an ancestor of a checkpoint. - * @returns {Boolean} - */ - -ChainEntry.prototype.isHistorical = function isHistorical() { - if (this.chain.options.checkpoints) { - if (this.height + 1 <= this.chain.network.lastCheckpoint) - return true; - } - return false; + return this.prevBlock === encoding.NULL_HASH; }; /** * Test whether the entry contains an unknown version bit. + * @param {Network} network * @returns {Boolean} */ -ChainEntry.prototype.hasUnknown = function hasUnknown() { +ChainEntry.prototype.hasUnknown = function hasUnknown(network) { const bits = this.version & consensus.VERSION_TOP_MASK; const topBits = consensus.VERSION_TOP_BITS; if ((bits >>> 0) !== topBits) return false; - return (this.version & this.chain.network.unknownBits) !== 0; + return (this.version & network.unknownBits) !== 0; }; /** @@ -375,14 +204,13 @@ ChainEntry.prototype.fromBlock = function fromBlock(block, prev) { /** * Instantiate chainentry from block. - * @param {Chain} chain * @param {Block|MerkleBlock} block * @param {ChainEntry} prev - Previous entry. * @returns {ChainEntry} */ -ChainEntry.fromBlock = function fromBlock(chain, block, prev) { - return new ChainEntry(chain).fromBlock(block, prev); +ChainEntry.fromBlock = function fromBlock(block, prev) { + return new ChainEntry().fromBlock(block, prev); }; /** @@ -432,13 +260,12 @@ ChainEntry.prototype.fromRaw = function fromRaw(data) { /** * Deserialize the entry. - * @param {Chain} chain * @param {Buffer} data * @returns {ChainEntry} */ -ChainEntry.fromRaw = function fromRaw(chain, data) { - return new ChainEntry(chain).fromRaw(data); +ChainEntry.fromRaw = function fromRaw(data) { + return new ChainEntry().fromRaw(data); }; /** @@ -493,13 +320,12 @@ ChainEntry.prototype.fromJSON = function fromJSON(json) { /** * Instantiate block from jsonified object. - * @param {Chain} chain * @param {Object} json * @returns {ChainEntry} */ -ChainEntry.fromJSON = function fromJSON(chain, json) { - return new ChainEntry(chain).fromJSON(json); +ChainEntry.fromJSON = function fromJSON(json) { + return new ChainEntry().fromJSON(json); }; /** @@ -541,14 +367,6 @@ ChainEntry.isChainEntry = function isChainEntry(obj) { return obj instanceof ChainEntry; }; -/* - * Helpers - */ - -function cmp(a, b) { - return a - b; -} - /* * Expose */ diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 76c19c5a4..af59e3928 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -503,7 +503,7 @@ RPC.prototype.getBlockchainInfo = async function getBlockchainInfo(args, help) { headers: this.chain.height, bestblockhash: this.chain.tip.rhash(), difficulty: toDifficulty(this.chain.tip.bits), - mediantime: await this.chain.tip.getMedianTime(), + mediantime: await this.chain.getMedianTime(this.chain.tip), verificationprogress: this.chain.getProgress(), chainwork: this.chain.tip.chainwork.toString('hex', 64), pruned: this.chain.options.prune, @@ -541,12 +541,12 @@ RPC.prototype.getBlock = async function getBlock(args, help) { if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); - const entry = await this.chain.db.getEntry(hash); + const entry = await this.chain.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found'); - const block = await this.chain.db.getBlock(entry.hash); + const block = await this.chain.getBlock(entry.hash); if (!block) { if (this.chain.options.spv) @@ -578,12 +578,12 @@ RPC.prototype.getBlockByHeight = async function getBlockByHeight(args, help) { if (height === -1) throw new RPCError(errs.TYPE_ERROR, 'Invalid block height.'); - const entry = await this.chain.db.getEntry(height); + const entry = await this.chain.getEntry(height); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found'); - const block = await this.chain.db.getBlock(entry.hash); + const block = await this.chain.getBlock(entry.hash); if (!block) { if (this.chain.options.spv) @@ -611,7 +611,7 @@ RPC.prototype.getBlockHash = async function getBlockHash(args, help) { if (height == null || height > this.chain.height) throw new RPCError(errs.INVALID_PARAMETER, 'Block height out of range.'); - const hash = await this.chain.db.getHash(height); + const hash = await this.chain.getHash(height); if (!hash) throw new RPCError(errs.MISC_ERROR, 'Not found.'); @@ -630,7 +630,7 @@ RPC.prototype.getBlockHeader = async function getBlockHeader(args, help) { if (!hash) throw new RPCError(errs.MISC_ERROR, 'Invalid block hash.'); - const entry = await this.chain.db.getEntry(hash); + const entry = await this.chain.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found'); @@ -645,16 +645,16 @@ RPC.prototype.getChainTips = async function getChainTips(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getchaintips'); - const tips = await this.chain.db.getTips(); + const tips = await this.chain.getTips(); const result = []; for (const hash of tips) { - const entry = await this.chain.db.getEntry(hash); + const entry = await this.chain.getEntry(hash); assert(entry); const fork = await this.findFork(entry); - const main = await entry.isMainChain(); + const main = await this.chain.isMainChain(entry); result.push({ height: entry.height, @@ -827,7 +827,7 @@ RPC.prototype.getTXOut = async function getTXOut(args, help) { } if (!coin) - coin = await this.chain.db.getCoin(hash, index); + coin = await this.chain.getCoin(hash, index); if (!coin) return null; @@ -885,15 +885,15 @@ RPC.prototype.getTXOutProof = async function getTXOutProof(args, help) { let block = null; if (hash) { - block = await this.chain.db.getBlock(hash); + block = await this.chain.getBlock(hash); } else if (this.chain.options.indexTX) { - const tx = await this.chain.db.getMeta(last); + const tx = await this.chain.getMeta(last); if (tx) - block = await this.chain.db.getBlock(tx.block); + block = await this.chain.getBlock(tx.block); } else { - const coin = await this.chain.db.getCoin(last, 0); + const coin = await this.chain.getCoin(last, 0); if (coin) - block = await this.chain.db.getBlock(coin.height); + block = await this.chain.getBlock(coin.height); } if (!block) @@ -926,7 +926,7 @@ RPC.prototype.verifyTXOutProof = async function verifyTXOutProof(args, help) { if (!block.verify()) return []; - const entry = await this.chain.db.getEntry(block.hash('hex')); + const entry = await this.chain.getEntry(block.hash('hex')); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found in chain.'); @@ -1765,7 +1765,7 @@ RPC.prototype.getRawTransaction = async function getRawTransaction(args, help) { let entry; if (meta.block) - entry = await this.chain.db.getEntry(meta.block); + entry = await this.chain.getEntry(meta.block); const json = this.txToJSON(tx, entry); json.time = meta.mtime; @@ -2326,7 +2326,7 @@ RPC.prototype.addBlock = async function addBlock(block) { RPC.prototype._addBlock = async function _addBlock(block) { this.logger.info('Handling submitted block: %s.', block.rhash()); - const prev = await this.chain.db.getEntry(block.prevBlock); + const prev = await this.chain.getEntry(block.prevBlock); if (prev) { const state = await this.chain.getDeployments(block.time, prev); @@ -2426,7 +2426,7 @@ RPC.prototype.getHashRate = async function getHashRate(lookup, height) { let tip = this.chain.tip; if (height != null) - tip = await this.chain.db.getEntry(height); + tip = await this.chain.getEntry(height); if (!tip) return 0; @@ -2445,7 +2445,7 @@ RPC.prototype.getHashRate = async function getHashRate(lookup, height) { let entry = tip; for (let i = 0; i < lookup; i++) { - entry = await entry.getPrevious(); + entry = await this.chain.getPrevious(entry); if (!entry) throw new RPCError(errs.DATABASE_ERROR, 'Not found.'); @@ -2488,9 +2488,9 @@ RPC.prototype._mineBlocks = async function _mineBlocks(blocks, addr, tries) { RPC.prototype.findFork = async function findFork(entry) { while (entry) { - if (await entry.isMainChain()) + if (await this.chain.isMainChain(entry)) return entry; - entry = await entry.getPrevious(); + entry = await this.chain.getPrevious(entry); } throw new Error('Fork not found.'); }; @@ -2598,8 +2598,8 @@ RPC.prototype.scriptToJSON = function scriptToJSON(script, hex) { }; RPC.prototype.headerToJSON = async function headerToJSON(entry) { - const mtp = await entry.getMedianTime(); - const next = await this.chain.db.getNextHash(entry.hash); + const mtp = await this.chain.getMedianTime(entry); + const next = await this.chain.getNextHash(entry.hash); return { hash: entry.rhash(), @@ -2621,8 +2621,8 @@ RPC.prototype.headerToJSON = async function headerToJSON(entry) { }; RPC.prototype.blockToJSON = async function blockToJSON(entry, block, details) { - const mtp = await entry.getMedianTime(); - const next = await this.chain.db.getNextHash(entry.hash); + const mtp = await this.chain.getMedianTime(entry); + const next = await this.chain.getNextHash(entry.hash); const txs = []; for (const tx of block.txs) { diff --git a/lib/http/server.js b/lib/http/server.js index 7b55b3341..e6b558a5f 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -264,21 +264,21 @@ HTTPServer.prototype.initRouter = function initRouter() { else hash = parseInt(hash, 10); - const block = await this.chain.db.getBlock(hash); + const block = await this.chain.getBlock(hash); if (!block) { res.send(404); return; } - const view = await this.chain.db.getBlockView(block); + const view = await this.chain.getBlockView(block); if (!view) { res.send(404); return; } - const height = await this.chain.db.getHeight(hash); + const height = await this.chain.getHeight(hash); res.send(200, block.getJSON(this.network, view, height)); }); @@ -441,12 +441,12 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { if (block == null) throw new Error('Invalid parameter.'); - const entry = await this.chain.db.getEntry(block); + const entry = await this.chain.getEntry(block); if (!entry) return null; - if (!await entry.isMainChain()) + if (!await this.chain.isMainChain(entry)) return null; return entry.toRaw(); diff --git a/lib/mining/miner.js b/lib/mining/miner.js index 6b19a254c..e6fdd7e0e 100644 --- a/lib/mining/miner.js +++ b/lib/mining/miner.js @@ -132,7 +132,7 @@ Miner.prototype._createBlock = async function _createBlock(tip, address) { if (version === -1) version = await this.chain.computeBlockVersion(tip); - const mtp = await tip.getMedianTime(); + const mtp = await this.chain.getMedianTime(tip); const time = Math.max(this.network.now(), mtp + 1); const state = await this.chain.getDeployments(time, tip); diff --git a/lib/net/pool.js b/lib/net/pool.js index 5b8b3fd1c..6df873cb3 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -1031,7 +1031,7 @@ Pool.prototype.getItem = async function getItem(peer, item) { if (this.chain.options.prune) return null; - return await this.chain.db.getBlock(item.hash); + return await this.chain.getBlock(item.hash); }; /** @@ -1061,7 +1061,7 @@ Pool.prototype.sendBlock = async function sendBlock(peer, item, witness) { // If we have the same serialization, we // can write the raw binary to the socket. if (witness || !this.options.hasWitness()) { - const block = await this.chain.db.getRawBlock(item.hash); + const block = await this.chain.getRawBlock(item.hash); if (block) { peer.sendRaw('block', block); @@ -1071,7 +1071,7 @@ Pool.prototype.sendBlock = async function sendBlock(peer, item, witness) { return false; } - const block = await this.chain.db.getBlock(item.hash); + const block = await this.chain.getBlock(item.hash); if (block) { peer.send(new packets.BlockPacket(block, witness)); @@ -1727,7 +1727,7 @@ Pool.prototype.handleBlockInv = async function handleBlockInv(peer, hashes) { // Attempt to update the peer's best height // with the last existing hash we know of. if (exists && this.chain.synced) { - const height = await this.chain.db.getHeight(exists); + const height = await this.chain.getHeight(exists); if (height !== -1) peer.bestHeight = height; } @@ -1849,7 +1849,7 @@ Pool.prototype.handleGetData = async function handleGetData(peer, packet) { break; } case invTypes.CMPCT_BLOCK: { - const height = await this.chain.db.getHeight(item.hash); + const height = await this.chain.getHeight(item.hash); // Fallback to full block. if (height < this.chain.tip.height - 10) { @@ -1962,7 +1962,7 @@ Pool.prototype.handleGetBlocks = async function handleGetBlocks(peer, packet) { let hash = await this.chain.findLocator(packet.locator); if (hash) - hash = await this.chain.db.getNextHash(hash); + hash = await this.chain.getNextHash(hash); const blocks = []; @@ -1977,7 +1977,7 @@ Pool.prototype.handleGetBlocks = async function handleGetBlocks(peer, packet) { break; } - hash = await this.chain.db.getNextHash(hash); + hash = await this.chain.getNextHash(hash); } peer.sendInv(blocks); @@ -2008,14 +2008,14 @@ Pool.prototype.handleGetHeaders = async function handleGetHeaders(peer, packet) if (packet.locator.length > 0) { hash = await this.chain.findLocator(packet.locator); if (hash) - hash = await this.chain.db.getNextHash(hash); + hash = await this.chain.getNextHash(hash); } else { hash = packet.stop; } let entry; if (hash) - entry = await this.chain.db.getEntry(hash); + entry = await this.chain.getEntry(hash); const headers = []; @@ -2028,7 +2028,7 @@ Pool.prototype.handleGetHeaders = async function handleGetHeaders(peer, packet) if (headers.length === 2000) break; - entry = await entry.getNext(); + entry = await this.chain.getNext(entry); } peer.sendHeaders(headers); @@ -2874,7 +2874,7 @@ Pool.prototype.handleGetBlockTxn = async function handleGetBlockTxn(peer, packet return; } - const height = await this.chain.db.getHeight(req.hash); + const height = await this.chain.getHeight(req.hash); if (height < this.chain.tip.height - 15) { this.logger.debug( diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 7be094159..de314ec1a 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -367,7 +367,7 @@ FullNode.prototype.stopSync = function stopSync() { */ FullNode.prototype.getBlock = function getBlock(hash) { - return this.chain.db.getBlock(hash); + return this.chain.getBlock(hash); }; /** @@ -387,7 +387,7 @@ FullNode.prototype.getCoin = async function getCoin(hash, index) { if (this.mempool.isSpent(hash, index)) return null; - return await this.chain.db.getCoin(hash, index); + return await this.chain.getCoin(hash, index); }; /** @@ -399,7 +399,7 @@ FullNode.prototype.getCoin = async function getCoin(hash, index) { FullNode.prototype.getCoinsByAddress = async function getCoinsByAddress(addrs) { const mempool = this.mempool.getCoinsByAddress(addrs); - const chain = await this.chain.db.getCoinsByAddress(addrs); + const chain = await this.chain.getCoinsByAddress(addrs); const out = []; for (const coin of chain) { @@ -426,7 +426,7 @@ FullNode.prototype.getCoinsByAddress = async function getCoinsByAddress(addrs) { FullNode.prototype.getMetaByAddress = async function getMetaByAddress(addrs) { const mempool = this.mempool.getMetaByAddress(addrs); - const chain = await this.chain.db.getMetaByAddress(addrs); + const chain = await this.chain.getMetaByAddress(addrs); return chain.concat(mempool); }; @@ -442,7 +442,7 @@ FullNode.prototype.getMeta = async function getMeta(hash) { if (meta) return meta; - return await this.chain.db.getMeta(hash); + return await this.chain.getMeta(hash); }; /** @@ -499,7 +499,7 @@ FullNode.prototype.hasTX = async function hasTX(hash) { if (this.mempool.hasEntry(hash)) return true; - return await this.chain.db.hasTX(hash); + return await this.chain.hasTX(hash); }; /* diff --git a/lib/wallet/nodeclient.js b/lib/wallet/nodeclient.js index d28cca2e8..f22dca2ef 100644 --- a/lib/wallet/nodeclient.js +++ b/lib/wallet/nodeclient.js @@ -102,12 +102,12 @@ NodeClient.prototype.getTip = function getTip() { */ NodeClient.prototype.getEntry = async function getEntry(hash) { - const entry = await this.node.chain.db.getEntry(hash); + const entry = await this.node.chain.getEntry(hash); if (!entry) return null; - if (!await entry.isMainChain()) + if (!await this.node.chain.isMainChain(entry)) return null; return entry; diff --git a/test/chain-test.js b/test/chain-test.js index c3785303b..8cfc2cebe 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -158,13 +158,13 @@ describe('Chain', function() { assert.strictEqual(chain.tip.hash, hash1); - tip1 = await chain.db.getEntry(hash1); - tip2 = await chain.db.getEntry(hash2); + tip1 = await chain.getEntry(hash1); + tip2 = await chain.getEntry(hash2); assert(tip1); assert(tip2); - assert(!await tip2.isMainChain()); + assert(!await chain.isMainChain(tip2)); } }); @@ -181,7 +181,7 @@ describe('Chain', function() { it('should handle a reorg', async () => { assert.strictEqual(chain.height, 210); - const entry = await chain.db.getEntry(tip2.hash); + const entry = await chain.getEntry(tip2.hash); assert(entry); assert.strictEqual(chain.height, entry.height); @@ -211,7 +211,7 @@ describe('Chain', function() { }); it('should check main chain', async () => { - const result = await tip1.isMainChain(); + const result = await chain.isMainChain(tip1); assert(!result); }); @@ -221,12 +221,12 @@ describe('Chain', function() { assert(await chain.add(block)); const hash = block.hash('hex'); - const entry = await chain.db.getEntry(hash); + const entry = await chain.getEntry(hash); assert(entry); assert.strictEqual(chain.tip.hash, entry.hash); - const result = await entry.isMainChain(); + const result = await chain.isMainChain(entry); assert(result); }); @@ -264,7 +264,7 @@ describe('Chain', function() { }); it('should fail to connect coins on an alternate chain', async () => { - const block = await chain.db.getBlock(tip1.hash); + const block = await chain.getBlock(tip1.hash); const cb = block.txs[0]; const mtx = new MTX(); @@ -314,7 +314,7 @@ describe('Chain', function() { const tx = block.txs[1]; const output = Coin.fromTX(tx, 2, chain.height); - const coin = await chain.db.getCoin(tx.hash('hex'), 2); + const coin = await chain.getCoin(tx.hash('hex'), 2); assert.bufferEqual(coin.toRaw(), output.toRaw()); }); @@ -347,7 +347,7 @@ describe('Chain', function() { it('should rescan for transactions', async () => { let total = 0; - await chain.db.scan(0, wallet.filter, async (block, txs) => { + await chain.scan(0, wallet.filter, async (block, txs) => { total += txs.length; }); @@ -361,7 +361,7 @@ describe('Chain', function() { assert.strictEqual(chain.height, 214); - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 1); @@ -370,19 +370,19 @@ describe('Chain', function() { assert(await chain.add(block)); switch (chain.height) { case 288: { - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 1); break; } case 432: { - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 2); break; } case 576: { - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 3); break; @@ -402,13 +402,13 @@ describe('Chain', function() { it('should have activated segwit', async () => { const deployments = network.deployments; - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.segwit); assert.strictEqual(state, 3); }); it('should test csv', async () => { - const tx = (await chain.db.getBlock(chain.height - 100)).txs[0]; + const tx = (await chain.getBlock(chain.height - 100)).txs[0]; const csvBlock = await mineCSV(tx); assert(await chain.add(csvBlock)); @@ -439,7 +439,7 @@ describe('Chain', function() { }); it('should fail csv with bad sequence', async () => { - const csv = (await chain.db.getBlock(chain.height - 100)).txs[0]; + const csv = (await chain.getBlock(chain.height - 100)).txs[0]; const spend = new MTX(); spend.addOutput({ @@ -468,7 +468,7 @@ describe('Chain', function() { }); it('should fail csv lock checks', async () => { - const tx = (await chain.db.getBlock(chain.height - 100)).txs[0]; + const tx = (await chain.getBlock(chain.height - 100)).txs[0]; const csvBlock = await mineCSV(tx); assert(await chain.add(csvBlock)); @@ -506,7 +506,7 @@ describe('Chain', function() { }); it('should fail to connect bad MTP', async () => { - const mtp = await chain.tip.getMedianTime(); + const mtp = await chain.getMedianTime(chain.tip); const job = await cpu.createJob(); job.attempt.time = mtp - 1; assert.strictEqual(await mineBlock(job), 'time-too-old'); @@ -613,7 +613,7 @@ describe('Chain', function() { }); it('should mine a witness tx', async () => { - const prev = await chain.db.getBlock(chain.height - 2000); + const prev = await chain.getBlock(chain.height - 2000); const cb = prev.txs[0]; const mtx = new MTX(); @@ -637,7 +637,7 @@ describe('Chain', function() { const job = await cpu.createJob(); for (let i = start; i <= end; i++) { - const block = await chain.db.getBlock(i); + const block = await chain.getBlock(i); const cb = block.txs[0]; const mtx = new MTX(); @@ -662,7 +662,7 @@ describe('Chain', function() { const job = await cpu.createJob(); for (let i = start; i <= end; i++) { - const block = await chain.db.getBlock(i); + const block = await chain.getBlock(i); const cb = block.txs[0]; const mtx = new MTX(); @@ -687,7 +687,7 @@ describe('Chain', function() { const job = await cpu.createJob(); for (let i = start; i <= end; i++) { - const block = await chain.db.getBlock(i); + const block = await chain.getBlock(i); const cb = block.txs[0]; const mtx = new MTX(); @@ -724,7 +724,7 @@ describe('Chain', function() { it('should fail to connect premature cb spend', async () => { const job = await cpu.createJob(); - const block = await chain.db.getBlock(chain.height - 98); + const block = await chain.getBlock(chain.height - 98); const cb = block.txs[0]; const mtx = new MTX(); @@ -742,7 +742,7 @@ describe('Chain', function() { it('should fail to connect vout belowout', async () => { const job = await cpu.createJob(); - const block = await chain.db.getBlock(chain.height - 99); + const block = await chain.getBlock(chain.height - 99); const cb = block.txs[0]; const mtx = new MTX(); @@ -760,7 +760,7 @@ describe('Chain', function() { it('should fail to connect outtotal toolarge', async () => { const job = await cpu.createJob(); - const block = await chain.db.getBlock(chain.height - 99); + const block = await chain.getBlock(chain.height - 99); const cb = block.txs[0]; const mtx = new MTX(); @@ -839,7 +839,7 @@ describe('Chain', function() { script.compile(); for (let i = start; i <= end; i++) { - const block = await chain.db.getBlock(i); + const block = await chain.getBlock(i); const cb = block.txs[0]; if (cb.outputs.length === 2) diff --git a/test/node-test.js b/test/node-test.js index a7dc5dea4..e400f4d8b 100644 --- a/test/node-test.js +++ b/test/node-test.js @@ -117,13 +117,13 @@ describe('Node', function() { assert.strictEqual(chain.tip.hash, block1.hash('hex')); - tip1 = await chain.db.getEntry(block1.hash('hex')); - tip2 = await chain.db.getEntry(block2.hash('hex')); + tip1 = await chain.getEntry(block1.hash('hex')); + tip2 = await chain.getEntry(block2.hash('hex')); assert(tip1); assert(tip2); - assert(!await tip2.isMainChain()); + assert(!await chain.isMainChain(tip2)); await co.wait(); } @@ -147,7 +147,7 @@ describe('Node', function() { assert.strictEqual(wdb.state.height, chain.height); assert.strictEqual(chain.height, 11); - const entry = await chain.db.getEntry(tip2.hash); + const entry = await chain.getEntry(tip2.hash); assert(entry); assert.strictEqual(chain.height, entry.height); @@ -181,7 +181,7 @@ describe('Node', function() { }); it('should check main chain', async () => { - const result = await tip1.isMainChain(); + const result = await chain.isMainChain(tip1); assert(!result); }); @@ -190,11 +190,11 @@ describe('Node', function() { await chain.add(block); - const entry = await chain.db.getEntry(block.hash('hex')); + const entry = await chain.getEntry(block.hash('hex')); assert(entry); assert.strictEqual(chain.tip.hash, entry.hash); - const result = await entry.isMainChain(); + const result = await chain.isMainChain(entry); assert(result); }); @@ -246,7 +246,7 @@ describe('Node', function() { const tx = block2.txs[1]; const output = Coin.fromTX(tx, 1, chain.height); - const coin = await chain.db.getCoin(tx.hash('hex'), 1); + const coin = await chain.getCoin(tx.hash('hex'), 1); assert.bufferEqual(coin.toRaw(), output.toRaw()); }); @@ -288,7 +288,7 @@ describe('Node', function() { it('should rescan for transactions', async () => { let total = 0; - await chain.db.scan(0, wdb.filter, async (block, txs) => { + await chain.scan(0, wdb.filter, async (block, txs) => { total += txs.length; }); @@ -298,7 +298,7 @@ describe('Node', function() { it('should activate csv', async () => { const deployments = chain.network.deployments; - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 0); @@ -307,19 +307,19 @@ describe('Node', function() { await chain.add(block); switch (chain.height) { case 144: { - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 1); break; } case 288: { - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 2); break; } case 432: { - const prev = await chain.tip.getPrevious(); + const prev = await chain.getPrevious(chain.tip); const state = await chain.getState(prev, deployments.csv); assert.strictEqual(state, 3); break; @@ -337,7 +337,7 @@ describe('Node', function() { }); it('should test csv', async () => { - const tx = (await chain.db.getBlock(chain.height)).txs[0]; + const tx = (await chain.getBlock(chain.height)).txs[0]; const csvBlock = await mineCSV(tx); await chain.add(csvBlock); @@ -368,7 +368,7 @@ describe('Node', function() { }); it('should fail csv with bad sequence', async () => { - const csv = (await chain.db.getBlock(chain.height)).txs[1]; + const csv = (await chain.getBlock(chain.height)).txs[1]; const spend = new MTX(); spend.addOutput({ @@ -407,7 +407,7 @@ describe('Node', function() { }); it('should fail csv lock checks', async () => { - const tx = (await chain.db.getBlock(chain.height)).txs[0]; + const tx = (await chain.getBlock(chain.height)).txs[0]; const csvBlock = await mineCSV(tx); await chain.add(csvBlock);