chain: improve target and mtp calculation.

This commit is contained in:
Christopher Jeffrey 2017-02-13 20:02:10 -08:00
parent dcf705d8bc
commit 1e07d1ba83
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 89 additions and 154 deletions

View File

@ -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;

View File

@ -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
*/

View File

@ -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 {

View File

@ -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();

View File

@ -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');