diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 4a54889f..3b19b0bf 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -266,8 +266,8 @@ Chain.prototype.isGenesis = function isGenesis(block) { Chain.prototype.verify = co(function* verify(block, prev) { var ret = new VerifyResult(); var now = this.network.now(); - var i, err, height, ts, tx, medianTime; - var commit, ancestors, state; + var i, err, height, ts, tx, mtp; + var commit, state, bits; // Non-contextual checks. if (!block.verify(ret)) { @@ -290,10 +290,10 @@ Chain.prototype.verify = co(function* verify(block, prev) { if (this.options.spv) return this.state; - ancestors = yield prev.getRetargetAncestors(); - // Ensure the POW is what we expect. - if (block.bits !== this.getTarget(block, prev, ancestors)) { + bits = yield this.getTarget(block.ts, prev); + + if (block.bits !== bits) { throw new VerifyError(block, 'invalid', 'bad-diffbits', @@ -301,9 +301,9 @@ Chain.prototype.verify = co(function* verify(block, prev) { } // Ensure the timestamp is correct. - medianTime = prev.getMedianTime(ancestors); + mtp = yield prev.getMedianTime(); - if (block.ts <= medianTime) { + if (block.ts <= mtp) { throw new VerifyError(block, 'invalid', 'time-too-old', @@ -326,7 +326,7 @@ Chain.prototype.verify = co(function* verify(block, prev) { state = yield this.getDeployments(block, prev); // Get timestamp for tx.isFinal(). - ts = state.hasMTP() ? medianTime : block.ts; + ts = state.hasMTP() ? mtp : block.ts; height = prev.height + 1; // Transactions must be finalized with @@ -1858,75 +1858,61 @@ Chain.prototype.getProofTime = function getProofTime(to, from) { */ Chain.prototype.getCurrentTarget = co(function* getCurrentTarget() { - return yield this.getTargetAsync(null, this.tip); + return yield this.getTarget(this.network.now(), this.tip); }); /** - * Calculate the target based on the passed-in chain entry. + * Calculate the next target. * @method + * @param {Number} ts - Next block timestamp. * @param {ChainEntry} prev - Previous entry. - * @param {Block} - Current block. * @returns {Promise} - returns Number * (target is in compact/mantissa form). */ -Chain.prototype.getTargetAsync = co(function* getTargetAsync(block, prev) { +Chain.prototype.getTarget = co(function* getTarget(ts, prev) { var pow = this.network.pow; - var ancestors; - - if ((prev.height + 1) % pow.retargetInterval !== 0) { - if (!pow.targetReset) - return this.getTarget(block, prev); - } - - ancestors = yield prev.getAncestors(pow.retargetInterval); - - return this.getTarget(block, prev, ancestors); -}); - -/** - * Calculate the target synchronously. _Must_ - * have ancestors pre-allocated. - * @param {Block} - Current block. - * @param {ChainEntry} prev - Previous entry. - * @param {ChainEntry[]} ancestors - * @returns {Promise} - returns Number - * (target is in compact/mantissa form). - */ - -Chain.prototype.getTarget = function getTarget(block, prev, ancestors) { - var pow = this.network.pow; - var ts, first, i; + var first, cache, height; // Genesis - if (!prev) + if (!prev) { + assert(ts === this.network.genesis.ts); return pow.bits; + } // Do not retarget if ((prev.height + 1) % pow.retargetInterval !== 0) { if (pow.targetReset) { // Special behavior for testnet: - ts = block ? (block.ts || block) : this.network.now(); if (ts > prev.ts + pow.targetSpacing * 2) return pow.bits; - i = 1; - while (ancestors[i] + while (prev.height !== 0 && prev.height % pow.retargetInterval !== 0 && prev.bits === pow.bits) { - prev = ancestors[i++]; + cache = prev.getPrevCache(); + + if (cache) { + prev = cache; + continue; + } + + prev = yield prev.getPrevious(); + assert(prev); } } return prev.bits; } // Back 2 weeks - first = ancestors[pow.retargetInterval - 1]; + height = prev.height - (pow.retargetInterval - 1); + assert(height >= 0); + first = yield prev.getAncestor(height); assert(first); return this.retarget(prev, first); -}; +}); /** * Retarget. This is called when the chain height @@ -2018,17 +2004,15 @@ Chain.prototype.getState = co(function* getState(prev, deployment) { var i, entry, count, state, cached; var block, time, height; - if (!prev) - return thresholdStates.DEFINED; - if (((prev.height + 1) % period) !== 0) { height = prev.height - ((prev.height + 1) % period); prev = yield prev.getAncestor(height); - if (prev) { - assert(prev.height === height); - assert(((prev.height + 1) % period) === 0); - } + if (!prev) + return thresholdStates.DEFINED; + + assert(prev.height === height); + assert(((prev.height + 1) % period) === 0); } entry = prev; @@ -2042,7 +2026,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) { break; } - time = yield entry.getMedianTimeAsync(); + time = yield entry.getMedianTime(); if (time < deployment.startTime) { state = thresholdStates.DEFINED; @@ -2061,7 +2045,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) { switch (state) { case thresholdStates.DEFINED: - time = yield entry.getMedianTimeAsync(); + time = yield entry.getMedianTime(); if (time >= deployment.timeout) { state = thresholdStates.FAILED; @@ -2075,7 +2059,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) { break; case thresholdStates.STARTED: - time = yield entry.getMedianTimeAsync(); + time = yield entry.getMedianTime(); if (time >= deployment.timeout) { state = thresholdStates.FAILED; @@ -2184,7 +2168,7 @@ Chain.prototype.verifyFinal = co(function* verifyFinal(prev, tx, flags) { return tx.isFinal(height, -1); if (flags & common.lockFlags.MEDIAN_TIME_PAST) { - ts = yield prev.getMedianTimeAsync(); + ts = yield prev.getMedianTime(); return tx.isFinal(height, ts); } @@ -2237,7 +2221,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, view, flags) { entry = yield prev.getAncestor(Math.max(coinHeight - 1, 0)); assert(entry, 'Database is corrupt.'); - coinTime = yield entry.getMedianTimeAsync(); + coinTime = yield entry.getMedianTime(); coinTime += ((input.sequence & mask) << granularity) - 1; minTime = Math.max(minTime, coinTime); } @@ -2257,7 +2241,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, view, flags) { Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, view, flags) { var locks = yield this.getLocks(prev, tx, view, flags); - var medianTime; + var mtp; // Also catches case where // height is `-1`. Fall through. @@ -2267,9 +2251,9 @@ Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, view, flags) { if (locks.time === -1) return true; - medianTime = yield prev.getMedianTimeAsync(); + mtp = yield prev.getMedianTime(); - if (locks.time >= medianTime) + if (locks.time >= mtp) return false; return true; diff --git a/lib/blockchain/chainentry.js b/lib/blockchain/chainentry.js index 83d31afb..6d7d6bd3 100644 --- a/lib/blockchain/chainentry.js +++ b/lib/blockchain/chainentry.js @@ -160,69 +160,6 @@ ChainEntry.prototype.isGenesis = function isGenesis() { return this.hash === this.chain.network.genesis.hash; }; -/** - * Allocate ancestors based on retarget interval and - * majority window. These ancestors will be stored - * in the `ancestors` array and enable use of synchronous - * ChainEntry methods. - * @returns {Promise} - */ - -ChainEntry.prototype.getRetargetAncestors = function getRetargetAncestors() { - var timespan = ChainEntry.MEDIAN_TIMESPAN; - var interval = this.chain.network.pow.retargetInterval; - var reset = this.chain.network.pow.targetReset; - var max = timespan; - - if ((this.height + 1) % interval === 0 || reset) - max = Math.max(max, interval); - - return this.getAncestors(max); -}; - -/** - * Collect ancestors. - * @method - * @param {Number} max - Number of ancestors. - * @returns {Promise} - Returns ChainEntry[]. - */ - -ChainEntry.prototype.getAncestors = co(function* getAncestors(max) { - var entry = this; - var ancestors = []; - var cached; - - if (max === 0) - return ancestors; - - assert(util.isNumber(max)); - - for (;;) { - ancestors.push(entry); - - if (ancestors.length >= max) - return ancestors; - - cached = this.chain.db.getCache(entry.prevBlock); - - if (!cached) { - ancestors.pop(); - break; - } - - entry = cached; - } - - while (entry) { - ancestors.push(entry); - if (ancestors.length >= max) - break; - entry = yield entry.getPrevious(); - } - - return ancestors; -}); - /** * Test whether the entry is in the main chain. * @method @@ -287,6 +224,15 @@ 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 @@ -320,36 +266,33 @@ ChainEntry.prototype.getNextEntry = co(function* getNextEntry() { }); /** - * Get median time past. - * @see GetMedianTimePast(). - * @param {ChainEntry[]} ancestors - Note that index 0 is the same entry. - * @returns {Number} Median time past. - */ - -ChainEntry.prototype.getMedianTime = function getMedianTime(ancestors) { - var timespan = ChainEntry.MEDIAN_TIMESPAN; - var entry = this; - var median = []; - var i; - - for (i = 0; i < timespan && entry; i++, entry = ancestors[i]) - median.push(entry.ts); - - median = median.sort(); - - return median[median.length / 2 | 0]; -}; - -/** - * Get median time past asynchronously (see {@link ChainEntry#getMedianTime}). + * Calculate median time past. * @method * @returns {Promise} - Returns Number. */ -ChainEntry.prototype.getMedianTimeAsync = co(function* getMedianTimeAsync() { +ChainEntry.prototype.getMedianTime = co(function* getMedianTime() { var timespan = ChainEntry.MEDIAN_TIMESPAN; - var ancestors = yield this.getAncestors(timespan); - return this.getMedianTime(ancestors); + var entry = this; + var median = []; + var i, cache; + + for (i = 0; i < timespan && entry; i++) { + median.push(entry.ts); + + cache = entry.getPrevCache(); + + if (cache) { + entry = cache; + continue; + } + + entry = yield entry.getPrevious(); + } + + median.sort(cmp); + + return median[median.length >>> 1]; }); /** @@ -383,7 +326,7 @@ ChainEntry.prototype.hasUnknown = function hasUnknown() { /** * Test whether the entry contains a version bit. - * @param {Object} deployment + * @param {Number} bit * @returns {Boolean} */ @@ -593,6 +536,14 @@ ChainEntry.isChainEntry = function isChainEntry(obj) { && typeof obj.getMedianTime === 'function'; }; +/* + * Helpers + */ + +function cmp(a, b) { + return a - b; +} + /* * Expose */ diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 744a0a33..4b3cfea6 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -683,7 +683,7 @@ RPC.prototype.getblockchaininfo = co(function* getblockchaininfo(args) { headers: this.chain.height, bestblockhash: this.chain.tip.rhash(), difficulty: this._getDifficulty(), - mediantime: yield this.chain.tip.getMedianTimeAsync(), + mediantime: yield this.chain.tip.getMedianTime(), verificationprogress: this.chain.getProgress(), chainwork: this.chain.tip.chainwork.toString('hex', 64), pruned: this.chain.options.prune, @@ -897,7 +897,7 @@ RPC.prototype.getblockheader = co(function* getblockheader(args) { }); RPC.prototype._headerToJSON = co(function* _headerToJSON(entry) { - var medianTime = yield entry.getMedianTimeAsync(); + var medianTime = yield entry.getMedianTime(); var nextHash = yield this.chain.db.getNextHash(entry.hash); return { @@ -920,7 +920,7 @@ RPC.prototype._headerToJSON = co(function* _headerToJSON(entry) { RPC.prototype._blockToJSON = co(function* _blockToJSON(entry, block, txDetails) { var self = this; - var medianTime = yield entry.getMedianTimeAsync(); + var medianTime = yield entry.getMedianTime(); var nextHash = yield this.chain.db.getNextHash(entry.hash); return { diff --git a/lib/mining/miner.js b/lib/mining/miner.js index 58cb0c77..16f74ebf 100644 --- a/lib/mining/miner.js +++ b/lib/mining/miner.js @@ -283,10 +283,10 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) { ts = Math.max(this.network.now(), tip.ts + 1); locktime = ts; - target = yield this.chain.getTargetAsync(ts, tip); + target = yield this.chain.getTarget(ts, tip); if (this.chain.state.hasMTP()) - locktime = yield tip.getMedianTimeAsync(); + locktime = yield tip.getMedianTime(); if (!address) address = this.getAddress(); diff --git a/test/chain-test.js b/test/chain-test.js index 5dc28797..a1c49fc7 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -460,7 +460,7 @@ describe('Chain', function() { })); it('should fail to connect bad MTP', co(function* () { - var mtp = yield chain.tip.getMedianTimeAsync(); + var mtp = yield chain.tip.getMedianTime(); var attempt = yield miner.createBlock(); attempt.block.ts = mtp - 1; assert.equal(yield addBlock(attempt), 'time-too-old');