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) {
|
Chain.prototype.verify = co(function* verify(block, prev) {
|
||||||
var ret = new VerifyResult();
|
var ret = new VerifyResult();
|
||||||
var now = this.network.now();
|
var now = this.network.now();
|
||||||
var i, err, height, ts, tx, medianTime;
|
var i, err, height, ts, tx, mtp;
|
||||||
var commit, ancestors, state;
|
var commit, state, bits;
|
||||||
|
|
||||||
// Non-contextual checks.
|
// Non-contextual checks.
|
||||||
if (!block.verify(ret)) {
|
if (!block.verify(ret)) {
|
||||||
@ -290,10 +290,10 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
|||||||
if (this.options.spv)
|
if (this.options.spv)
|
||||||
return this.state;
|
return this.state;
|
||||||
|
|
||||||
ancestors = yield prev.getRetargetAncestors();
|
|
||||||
|
|
||||||
// Ensure the POW is what we expect.
|
// 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,
|
throw new VerifyError(block,
|
||||||
'invalid',
|
'invalid',
|
||||||
'bad-diffbits',
|
'bad-diffbits',
|
||||||
@ -301,9 +301,9 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the timestamp is correct.
|
// 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,
|
throw new VerifyError(block,
|
||||||
'invalid',
|
'invalid',
|
||||||
'time-too-old',
|
'time-too-old',
|
||||||
@ -326,7 +326,7 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
|||||||
state = yield this.getDeployments(block, prev);
|
state = yield this.getDeployments(block, prev);
|
||||||
|
|
||||||
// Get timestamp for tx.isFinal().
|
// Get timestamp for tx.isFinal().
|
||||||
ts = state.hasMTP() ? medianTime : block.ts;
|
ts = state.hasMTP() ? mtp : block.ts;
|
||||||
height = prev.height + 1;
|
height = prev.height + 1;
|
||||||
|
|
||||||
// Transactions must be finalized with
|
// Transactions must be finalized with
|
||||||
@ -1858,75 +1858,61 @@ Chain.prototype.getProofTime = function getProofTime(to, from) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Chain.prototype.getCurrentTarget = co(function* getCurrentTarget() {
|
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
|
* @method
|
||||||
|
* @param {Number} ts - Next block timestamp.
|
||||||
* @param {ChainEntry} prev - Previous entry.
|
* @param {ChainEntry} prev - Previous entry.
|
||||||
* @param {Block} - Current block.
|
|
||||||
* @returns {Promise} - returns Number
|
* @returns {Promise} - returns Number
|
||||||
* (target is in compact/mantissa form).
|
* (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 pow = this.network.pow;
|
||||||
var ancestors;
|
var first, cache, height;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Genesis
|
// Genesis
|
||||||
if (!prev)
|
if (!prev) {
|
||||||
|
assert(ts === this.network.genesis.ts);
|
||||||
return pow.bits;
|
return pow.bits;
|
||||||
|
}
|
||||||
|
|
||||||
// Do not retarget
|
// Do not retarget
|
||||||
if ((prev.height + 1) % pow.retargetInterval !== 0) {
|
if ((prev.height + 1) % pow.retargetInterval !== 0) {
|
||||||
if (pow.targetReset) {
|
if (pow.targetReset) {
|
||||||
// Special behavior for testnet:
|
// Special behavior for testnet:
|
||||||
ts = block ? (block.ts || block) : this.network.now();
|
|
||||||
if (ts > prev.ts + pow.targetSpacing * 2)
|
if (ts > prev.ts + pow.targetSpacing * 2)
|
||||||
return pow.bits;
|
return pow.bits;
|
||||||
|
|
||||||
i = 1;
|
while (prev.height !== 0
|
||||||
while (ancestors[i]
|
|
||||||
&& prev.height % pow.retargetInterval !== 0
|
&& prev.height % pow.retargetInterval !== 0
|
||||||
&& prev.bits === pow.bits) {
|
&& prev.bits === pow.bits) {
|
||||||
prev = ancestors[i++];
|
cache = prev.getPrevCache();
|
||||||
|
|
||||||
|
if (cache) {
|
||||||
|
prev = cache;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = yield prev.getPrevious();
|
||||||
|
assert(prev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return prev.bits;
|
return prev.bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back 2 weeks
|
// Back 2 weeks
|
||||||
first = ancestors[pow.retargetInterval - 1];
|
height = prev.height - (pow.retargetInterval - 1);
|
||||||
|
assert(height >= 0);
|
||||||
|
|
||||||
|
first = yield prev.getAncestor(height);
|
||||||
assert(first);
|
assert(first);
|
||||||
|
|
||||||
return this.retarget(prev, first);
|
return this.retarget(prev, first);
|
||||||
};
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retarget. This is called when the chain height
|
* 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 i, entry, count, state, cached;
|
||||||
var block, time, height;
|
var block, time, height;
|
||||||
|
|
||||||
if (!prev)
|
|
||||||
return thresholdStates.DEFINED;
|
|
||||||
|
|
||||||
if (((prev.height + 1) % period) !== 0) {
|
if (((prev.height + 1) % period) !== 0) {
|
||||||
height = prev.height - ((prev.height + 1) % period);
|
height = prev.height - ((prev.height + 1) % period);
|
||||||
prev = yield prev.getAncestor(height);
|
prev = yield prev.getAncestor(height);
|
||||||
|
|
||||||
if (prev) {
|
if (!prev)
|
||||||
assert(prev.height === height);
|
return thresholdStates.DEFINED;
|
||||||
assert(((prev.height + 1) % period) === 0);
|
|
||||||
}
|
assert(prev.height === height);
|
||||||
|
assert(((prev.height + 1) % period) === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = prev;
|
entry = prev;
|
||||||
@ -2042,7 +2026,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
time = yield entry.getMedianTimeAsync();
|
time = yield entry.getMedianTime();
|
||||||
|
|
||||||
if (time < deployment.startTime) {
|
if (time < deployment.startTime) {
|
||||||
state = thresholdStates.DEFINED;
|
state = thresholdStates.DEFINED;
|
||||||
@ -2061,7 +2045,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
|
|||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case thresholdStates.DEFINED:
|
case thresholdStates.DEFINED:
|
||||||
time = yield entry.getMedianTimeAsync();
|
time = yield entry.getMedianTime();
|
||||||
|
|
||||||
if (time >= deployment.timeout) {
|
if (time >= deployment.timeout) {
|
||||||
state = thresholdStates.FAILED;
|
state = thresholdStates.FAILED;
|
||||||
@ -2075,7 +2059,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case thresholdStates.STARTED:
|
case thresholdStates.STARTED:
|
||||||
time = yield entry.getMedianTimeAsync();
|
time = yield entry.getMedianTime();
|
||||||
|
|
||||||
if (time >= deployment.timeout) {
|
if (time >= deployment.timeout) {
|
||||||
state = thresholdStates.FAILED;
|
state = thresholdStates.FAILED;
|
||||||
@ -2184,7 +2168,7 @@ Chain.prototype.verifyFinal = co(function* verifyFinal(prev, tx, flags) {
|
|||||||
return tx.isFinal(height, -1);
|
return tx.isFinal(height, -1);
|
||||||
|
|
||||||
if (flags & common.lockFlags.MEDIAN_TIME_PAST) {
|
if (flags & common.lockFlags.MEDIAN_TIME_PAST) {
|
||||||
ts = yield prev.getMedianTimeAsync();
|
ts = yield prev.getMedianTime();
|
||||||
return tx.isFinal(height, ts);
|
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));
|
entry = yield prev.getAncestor(Math.max(coinHeight - 1, 0));
|
||||||
assert(entry, 'Database is corrupt.');
|
assert(entry, 'Database is corrupt.');
|
||||||
|
|
||||||
coinTime = yield entry.getMedianTimeAsync();
|
coinTime = yield entry.getMedianTime();
|
||||||
coinTime += ((input.sequence & mask) << granularity) - 1;
|
coinTime += ((input.sequence & mask) << granularity) - 1;
|
||||||
minTime = Math.max(minTime, coinTime);
|
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) {
|
Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, view, flags) {
|
||||||
var locks = yield this.getLocks(prev, tx, view, flags);
|
var locks = yield this.getLocks(prev, tx, view, flags);
|
||||||
var medianTime;
|
var mtp;
|
||||||
|
|
||||||
// Also catches case where
|
// Also catches case where
|
||||||
// height is `-1`. Fall through.
|
// height is `-1`. Fall through.
|
||||||
@ -2267,9 +2251,9 @@ Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, view, flags) {
|
|||||||
if (locks.time === -1)
|
if (locks.time === -1)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
medianTime = yield prev.getMedianTimeAsync();
|
mtp = yield prev.getMedianTime();
|
||||||
|
|
||||||
if (locks.time >= medianTime)
|
if (locks.time >= mtp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -160,69 +160,6 @@ ChainEntry.prototype.isGenesis = function isGenesis() {
|
|||||||
return this.hash === this.chain.network.genesis.hash;
|
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.
|
* Test whether the entry is in the main chain.
|
||||||
* @method
|
* @method
|
||||||
@ -287,6 +224,15 @@ ChainEntry.prototype.getPrevious = function getPrevious() {
|
|||||||
return this.chain.db.getEntry(this.prevBlock);
|
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.
|
* Get next entry.
|
||||||
* @method
|
* @method
|
||||||
@ -320,36 +266,33 @@ ChainEntry.prototype.getNextEntry = co(function* getNextEntry() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get median time past.
|
* Calculate 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}).
|
|
||||||
* @method
|
* @method
|
||||||
* @returns {Promise} - Returns Number.
|
* @returns {Promise} - Returns Number.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ChainEntry.prototype.getMedianTimeAsync = co(function* getMedianTimeAsync() {
|
ChainEntry.prototype.getMedianTime = co(function* getMedianTime() {
|
||||||
var timespan = ChainEntry.MEDIAN_TIMESPAN;
|
var timespan = ChainEntry.MEDIAN_TIMESPAN;
|
||||||
var ancestors = yield this.getAncestors(timespan);
|
var entry = this;
|
||||||
return this.getMedianTime(ancestors);
|
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.
|
* Test whether the entry contains a version bit.
|
||||||
* @param {Object} deployment
|
* @param {Number} bit
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -593,6 +536,14 @@ ChainEntry.isChainEntry = function isChainEntry(obj) {
|
|||||||
&& typeof obj.getMedianTime === 'function';
|
&& typeof obj.getMedianTime === 'function';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function cmp(a, b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -683,7 +683,7 @@ RPC.prototype.getblockchaininfo = co(function* getblockchaininfo(args) {
|
|||||||
headers: this.chain.height,
|
headers: this.chain.height,
|
||||||
bestblockhash: this.chain.tip.rhash(),
|
bestblockhash: this.chain.tip.rhash(),
|
||||||
difficulty: this._getDifficulty(),
|
difficulty: this._getDifficulty(),
|
||||||
mediantime: yield this.chain.tip.getMedianTimeAsync(),
|
mediantime: yield this.chain.tip.getMedianTime(),
|
||||||
verificationprogress: this.chain.getProgress(),
|
verificationprogress: this.chain.getProgress(),
|
||||||
chainwork: this.chain.tip.chainwork.toString('hex', 64),
|
chainwork: this.chain.tip.chainwork.toString('hex', 64),
|
||||||
pruned: this.chain.options.prune,
|
pruned: this.chain.options.prune,
|
||||||
@ -897,7 +897,7 @@ RPC.prototype.getblockheader = co(function* getblockheader(args) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
RPC.prototype._headerToJSON = co(function* _headerToJSON(entry) {
|
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);
|
var nextHash = yield this.chain.db.getNextHash(entry.hash);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -920,7 +920,7 @@ RPC.prototype._headerToJSON = co(function* _headerToJSON(entry) {
|
|||||||
|
|
||||||
RPC.prototype._blockToJSON = co(function* _blockToJSON(entry, block, txDetails) {
|
RPC.prototype._blockToJSON = co(function* _blockToJSON(entry, block, txDetails) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var medianTime = yield entry.getMedianTimeAsync();
|
var medianTime = yield entry.getMedianTime();
|
||||||
var nextHash = yield this.chain.db.getNextHash(entry.hash);
|
var nextHash = yield this.chain.db.getNextHash(entry.hash);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -283,10 +283,10 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) {
|
|||||||
ts = Math.max(this.network.now(), tip.ts + 1);
|
ts = Math.max(this.network.now(), tip.ts + 1);
|
||||||
locktime = ts;
|
locktime = ts;
|
||||||
|
|
||||||
target = yield this.chain.getTargetAsync(ts, tip);
|
target = yield this.chain.getTarget(ts, tip);
|
||||||
|
|
||||||
if (this.chain.state.hasMTP())
|
if (this.chain.state.hasMTP())
|
||||||
locktime = yield tip.getMedianTimeAsync();
|
locktime = yield tip.getMedianTime();
|
||||||
|
|
||||||
if (!address)
|
if (!address)
|
||||||
address = this.getAddress();
|
address = this.getAddress();
|
||||||
|
|||||||
@ -460,7 +460,7 @@ describe('Chain', function() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should fail to connect bad MTP', co(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();
|
var attempt = yield miner.createBlock();
|
||||||
attempt.block.ts = mtp - 1;
|
attempt.block.ts = mtp - 1;
|
||||||
assert.equal(yield addBlock(attempt), 'time-too-old');
|
assert.equal(yield addBlock(attempt), 'time-too-old');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user