implement GetAdjustedTime.

This commit is contained in:
Christopher Jeffrey 2016-04-19 05:47:47 -07:00
parent a871054387
commit b9369412d2
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
10 changed files with 171 additions and 40 deletions

View File

@ -106,10 +106,11 @@ AbstractBlock.prototype.verify = function verify(ret) {
* all objects which inherit from AbstractBlock). * all objects which inherit from AbstractBlock).
* @param {Object?} ret - Return object, may be * @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`. * set with properties `reason` and `score`.
* @param {Number?} now - Hopefully the adjusted time.
* @returns {Boolean} * @returns {Boolean}
*/ */
AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) { AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret, now) {
if (!ret) if (!ret)
ret = {}; ret = {};
@ -120,8 +121,11 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
return false; return false;
} }
if (now == null)
now = utils.now();
// Check timestamp against now + 2 hours // Check timestamp against now + 2 hours
if (this.ts > utils.now() + 2 * 60 * 60) { if (this.ts > now + 2 * 60 * 60) {
ret.reason = 'time-too-new'; ret.reason = 'time-too-new';
ret.score = 0; ret.score = 0;
return false; return false;

108
lib/bcoin/adjustedtime.js Normal file
View File

@ -0,0 +1,108 @@
/*!
* adjustedtime.js - time management for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/indutny/bcoin
*/
module.exports = function(bcoin) {
var EventEmitter = require('events').EventEmitter;
var utils = require('./utils');
var assert = utils.assert;
/**
* An object which handles "adjusted time". This may not
* look it, but this is actually a semi-consensus-critical
* piece of code. It handles version packets from peers
* and calculates what to offset our system clock's time by.
* @exports AdjustedTime
* @constructor
* @param {Number} [limit=200]
* @property {Array} samples
* @property {Object} known
*/
function AdjustedTime(limit) {
if (!(this instanceof AdjustedTime))
return new AdjustedTime(limit);
EventEmitter.call(this);
if (limit == null)
limit = 200;
this.samples = [];
this.known = {};
this._checked = false;
}
utils.inherits(AdjustedTime, EventEmitter);
/**
* Add time data.
* @param {String} host
* @param {Number} time
*/
AdjustedTime.prototype.add = function add(host, time) {
var sample = time - utils.now();
var i, median, match, offset;
if (this.samples.length >= 200)
return;
if (this.known[host])
return;
this.known[host] = sample;
i = utils.binarySearch(this.samples, sample, true, compare);
this.samples.splice(i + 1, 0, sample);
bcoin.debug('Added time data: samples=%d, offset=%d (%d minutes)',
this.samples.length, sample, sample / 60 | 0);
if (this.samples.length >= 5 && this.samples.length % 2 === 1) {
median = this.samples[this.samples / 2 | 0];
if (Math.abs(median) < 70 * 60) {
this.offset = median;
} else {
this.offset = 0;
if (!this._checked) {
match = false;
for (i = 0; i < this.samples.length; i++) {
offset = this.samples[i];
if (offset !== 0 && Math.abs(offset) < 5 * 60) {
match = true;
break;
}
if (!match) {
this._checked = true;
bcoin.debug('Please make sure your system clock is correct!');
}
}
}
}
bcoin.debug('Time offset: %d (%d minutes)',
this.offset, this.offset / 60 | 0);
}
};
/**
* Get the current adjusted time.
* @returns {Number} Time.
*/
AdjustedTime.prototype.now = function now() {
return utils.now() + this.offset;
};
function compare(a, b) {
return a - b;
}
return AdjustedTime;
};

View File

@ -322,17 +322,18 @@ Block.prototype.__defineGetter__('commitmentHash', function() {
* @alias verify * @alias verify
* @param {Object?} ret - Return object, may be * @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`. * set with properties `reason` and `score`.
* @param {Number?} now - Hopefully the adjusted time.
* @returns {Boolean} * @returns {Boolean}
*/ */
Block.prototype._verify = function _verify(ret) { Block.prototype._verify = function _verify(ret, now) {
var sigops = 0; var sigops = 0;
var i, tx, merkle; var i, tx, merkle;
if (!ret) if (!ret)
ret = {}; ret = {};
if (!this.verifyHeaders(ret)) if (!this.verifyHeaders(ret, now))
return false; return false;
// Size can't be bigger than MAX_BLOCK_SIZE // Size can't be bigger than MAX_BLOCK_SIZE

View File

@ -679,7 +679,7 @@ Iterator.prototype.seek = function seek(key) {
if (typeof key === 'string') if (typeof key === 'string')
key = new Buffer(key, 'ascii'); key = new Buffer(key, 'ascii');
this.index = binarySearch(this.items, key, true, function(a, b) { this.index = utils.binarySearch(this.items, key, true, function(a, b) {
return self.tree.compare(a.key, b); return self.tree.compare(a.key, b);
}); });
}; };
@ -707,33 +707,6 @@ Iterator.prototype.end = function end(callback) {
* Helpers * Helpers
*/ */
function binarySearch(items, key, insert, compare) {
var start = 0;
var end = items.length - 1;
var pos, cmp;
if (!compare)
compare = utils.cmp;
while (start <= end) {
pos = (start + end) >>> 1;
cmp = compare(items[pos], key);
if (cmp === 0)
return pos;
if (cmp < 0)
start = pos + 1;
else
end = pos - 1;
}
if (!insert)
return -1;
return start - 1;
}
function clone(node) { function clone(node) {
return { return {
key: node.key, key: node.key,

View File

@ -67,6 +67,7 @@ function Chain(options) {
this.options = options; this.options = options;
this.loaded = false; this.loaded = false;
this.time = options.time || new bcoin.adjustedtime();
this.db = new bcoin.chaindb(this, options); this.db = new bcoin.chaindb(this, options);
this.total = 0; this.total = 0;
this.orphanLimit = options.orphanLimit || (20 << 20); this.orphanLimit = options.orphanLimit || (20 << 20);
@ -479,7 +480,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
callback(err, result); callback(err, result);
} }
if (!block.verify(ret)) if (!block.verify(ret, this.time.now()))
return done(new VerifyError(block, 'invalid', ret.reason, ret.score)); return done(new VerifyError(block, 'invalid', ret.reason, ret.score));
if (this.options.spv || block.type !== 'block') if (this.options.spv || block.type !== 'block')
@ -1397,7 +1398,7 @@ Chain.prototype.add = function add(block, callback, force) {
// This is only necessary for new // This is only necessary for new
// blocks coming in, not the resolving // blocks coming in, not the resolving
// orphans. // orphans.
if (initial && !block.verify(ret)) { if (initial && !block.verify(ret, self.time.now())) {
self.invalid[hash] = true; self.invalid[hash] = true;
self.emit('invalid', block, { self.emit('invalid', block, {
height: block.getCoinbaseHeight(), height: block.getCoinbaseHeight(),

View File

@ -187,6 +187,7 @@ function Environment(options) {
this.protocol = require('./protocol')(this); this.protocol = require('./protocol')(this);
this.profiler = require('./profiler')(this); this.profiler = require('./profiler')(this);
this.ldb = require('./ldb')(this); this.ldb = require('./ldb')(this);
this.adjustedtime = require('./adjustedtime')(this);
this.script = require('./script')(this); this.script = require('./script')(this);
this.stack = this.script.stack; this.stack = this.script.stack;
this.witness = this.script.witness; this.witness = this.script.witness;

View File

@ -52,11 +52,12 @@ Headers.prototype.render = function render() {
* @alias verify * @alias verify
* @param {Object?} ret - Return object, may be * @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`. * set with properties `reason` and `score`.
* @param {Number?} now - Hopefully the adjusted time.
* @returns {Boolean} * @returns {Boolean}
*/ */
Headers.prototype._verify = function _verify() { Headers.prototype._verify = function _verify(ret, now) {
return this.verifyHeaders(); return this.verifyHeaders(ret, now);
}; };
/** /**

View File

@ -204,14 +204,15 @@ MerkleBlock.prototype.verifyPartial = function verifyPartial() {
* @alias verify * @alias verify
* @param {Object?} ret - Return object, may be * @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`. * set with properties `reason` and `score`.
* @param {Number?} now - Hopefully the adjusted time.
* @returns {Boolean} * @returns {Boolean}
*/ */
MerkleBlock.prototype._verify = function _verify(ret) { MerkleBlock.prototype._verify = function _verify(ret, now) {
if (!ret) if (!ret)
ret = {}; ret = {};
if (!this.verifyHeaders(ret)) if (!this.verifyHeaders(ret, now))
return false; return false;
// Verify the partial merkle tree if we are a merkleblock. // Verify the partial merkle tree if we are a merkleblock.

View File

@ -81,6 +81,9 @@ function Pool(options) {
this.chain = options.chain; this.chain = options.chain;
this.mempool = options.mempool; this.mempool = options.mempool;
this.time = options.time || new bcoin.adjustedtime();
this.chain.time = this.time;
assert(this.chain, 'Pool requires a blockchain.'); assert(this.chain, 'Pool requires a blockchain.');
if (options.relay == null) { if (options.relay == null) {
@ -648,6 +651,7 @@ Pool.prototype.stopSync = function stopSync() {
Pool.prototype._handleHeaders = function _handleHeaders(headers, peer, callback) { Pool.prototype._handleHeaders = function _handleHeaders(headers, peer, callback) {
var self = this; var self = this;
var ret = {};
var last; var last;
assert(this.options.headers); assert(this.options.headers);
@ -678,8 +682,8 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer, callback)
if (last && header.prevBlock !== last) if (last && header.prevBlock !== last)
return next(new Error('Bad header chain.')); return next(new Error('Bad header chain.'));
if (!header.verify()) if (!header.verify(ret, self.time.now()))
return next(new Error('Headers invalid.')); return next(new VerifyError(header, 'invalid', ret.reason, ret.score));
last = hash; last = hash;
@ -1002,6 +1006,7 @@ Pool.prototype._createPeer = function _createPeer(options) {
'Received version from %s: version=%d height=%d agent=%s', 'Received version from %s: version=%d height=%d agent=%s',
peer.host, version.version, version.height, version.agent); peer.host, version.version, version.height, version.agent);
self.time.add(peer.host, version.ts);
self.emit('version', version, peer); self.emit('version', version, peer);
}); });

View File

@ -2735,6 +2735,42 @@ utils.revMap = function revMap(map) {
return reversed; return reversed;
}; };
/**
* Perform a binary search on a sorted array.
* @param {Array} items
* @param {Object} key
* @param {Boolean?} insert
* @param {Function?} compare
* @returns {Number} Index.
*/
utils.binarySearch = function binarySearch(items, key, insert, compare) {
var start = 0;
var end = items.length - 1;
var pos, cmp;
if (!compare)
compare = utils.cmp;
while (start <= end) {
pos = (start + end) >>> 1;
cmp = compare(items[pos], key);
if (cmp === 0)
return pos;
if (cmp < 0)
start = pos + 1;
else
end = pos - 1;
}
if (!insert)
return -1;
return start - 1;
};
/** /**
* An error thrown during verification. Can be either * An error thrown during verification. Can be either
* a mempool transaction validation error or a blockchain * a mempool transaction validation error or a blockchain