chain: improve target and mtp calculation.
This commit is contained in:
parent
dcf705d8bc
commit
1e07d1ba83
@ -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;
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user