net: better headers first.

This commit is contained in:
Christopher Jeffrey 2017-01-19 04:35:12 -08:00
parent 6bc016f313
commit 66ec0ae36e
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 228 additions and 57 deletions

View File

@ -1685,15 +1685,17 @@ Chain.prototype.isInitial = function isInitial(force) {
if (!force && this.synced)
return false;
if (this.height < this.network.checkpoints.lastHeight)
if (this.options.useCheckpoints) {
if (this.height < this.network.lastCheckpoint)
return true;
}
if (this.tip.ts < util.now() - this.network.block.maxTipAge)
return true;
if (this.tip.chainwork.cmp(this.network.pow.chainwork) < 0)
return true;
if (this.tip.ts < util.now() - this.network.block.maxTipAge)
return true;
return false;
};

View File

@ -354,7 +354,7 @@ ChainEntry.prototype.getMedianTimeAsync = co(function* getMedianTimeAsync() {
ChainEntry.prototype.isHistorical = function isHistorical() {
if (this.chain.options.useCheckpoints) {
if (this.height + 1 <= this.chain.network.checkpoints.lastHeight)
if (this.height + 1 <= this.chain.network.lastCheckpoint)
return true;
}
return false;

View File

@ -616,12 +616,6 @@ Peer.prototype.finalize = co(function* finalize() {
this.send(new packets.AddrPacket([this.pool.address]));
}
// Ask for headers-only.
if (this.options.headers) {
if (this.version >= common.SENDHEADERS_VERSION)
this.send(new packets.SendHeadersPacket());
}
// We want compact blocks!
if (this.options.compact) {
if (this.version >= common.COMPACT_VERSION)
@ -3075,7 +3069,7 @@ Peer.prototype.getBlocks = co(function* getBlocks(tip, stop) {
*/
Peer.prototype.sync = co(function* sync() {
var tip;
var tip, checkpoint;
if (!this.pool.syncing)
return;
@ -3107,11 +3101,11 @@ Peer.prototype.sync = co(function* sync() {
this.lastBlock = util.ms();
if (this.options.headers) {
if (!this.chain.tip.isGenesis())
tip = this.chain.tip.prevBlock;
return yield this.getHeaders(tip);
if (this.pool.headersFirst) {
tip = this.chain.tip;
checkpoint = this.pool.nextCheckpoint;
this.sendGetHeaders([tip.hash], checkpoint.hash);
return;
}
return yield this.getBlocks();

View File

@ -32,7 +32,6 @@ var InvItem = require('../primitives/invitem');
var Map = require('../utils/map');
var invTypes = InvItem.types;
var VerifyError = errors.VerifyError;
var VerifyResult = errors.VerifyResult;
/**
* A pool of peers for handling all network activity.
@ -113,6 +112,12 @@ function Pool(options) {
this.pendingWatch = null;
this.pendingRefill = null;
this.headersFirst = false;
this.headerChain = new List();
this.headerNext = null;
this.nextCheckpoint = null;
this.checkpoints = [];
this.peers = new PeerList();
this.authdb = new BIP150.AuthDB(this.options);
this.address = new NetAddress(this.options);
@ -138,6 +143,18 @@ util.inherits(Pool, AsyncObject);
Pool.prototype._init = function _init() {
var self = this;
var keys = Object.keys(this.network.checkpoints);
var i, key, hash, height;
for (i = 0; i < keys.length; i++) {
key = keys[i];
hash = this.network.checkpoints[key];
height = +key;
this.checkpoints.push(new BlockNode(hash, height));
}
this.checkpoints.sort(sortNodes);
this.server.on('error', function(err) {
self.emit('error', err);
@ -232,8 +249,42 @@ Pool.prototype._open = co(function* _open() {
this.logger.info('Identity public key: %s.', key.toString('hex'));
this.logger.info('Identity address: %s.', BIP150.address(key));
}
this.resetChain();
});
/**
* Reset header chain.
*/
Pool.prototype.resetChain = function resetChain() {
var tip = this.chain.tip;
var checkpoint;
if (!this.options.headers)
return;
if (!this.options.useCheckpoints)
return;
this.headersFirst = false;
this.nextCheckpoint = null;
this.headerChain.reset();
this.headerNext = null;
checkpoint = this.getNextCheckpoint(tip.height);
if (checkpoint) {
this.headersFirst = true;
this.nextCheckpoint = checkpoint;
this.headerChain.push(new BlockNode(tip.hash, tip.height));
this.logger.info(
'Initialized header chain to height %d (checkpoint=%d).',
tip.height,
checkpoint.height);
}
};
/**
* Close and destroy the pool.
* @alias Pool#close
@ -287,6 +338,7 @@ Pool.prototype._connect = co(function* connect() {
}
yield this.authdb.discover();
yield this.hosts.discover();
if (this.hosts.size() === 0)
@ -348,6 +400,11 @@ Pool.prototype._disconnect = co(function* disconnect() {
this.pendingRefill = null;
}
this.headersFirst = false;
this.nextCheckpoint = null;
this.headerChain.reset();
this.headerNext = null;
yield this.unlisten();
this.syncing = false;
@ -708,8 +765,10 @@ Pool.prototype.handleClose = co(function* handleClose(peer, connected) {
this.removePeer(peer);
if (loader)
if (loader) {
this.logger.info('Removed loader peer (%s).', peer.hostname);
this.resetChain();
}
if (!this.loaded)
return;
@ -800,7 +859,8 @@ Pool.prototype.handleBlock = co(function* handleBlock(peer, block) {
Pool.prototype._handleBlock = co(function* handleBlock(peer, block) {
var hash = block.hash('hex');
var requested;
var isCheckpoint = false;
var requested, node, checkpoint;
if (!this.syncing)
return;
@ -817,6 +877,22 @@ Pool.prototype._handleBlock = co(function* handleBlock(peer, block) {
return;
}
if (this.headersFirst) {
node = this.headerChain.head;
assert(node);
if (hash === node.hash) {
if (hash === this.nextCheckpoint.hash) {
this.logger.info(
'Received checkpoint block %s (%d).',
hash, node.height);
isCheckpoint = true;
} else {
this.headerChain.shift();
}
}
}
try {
yield this.chain.add(block);
} catch (err) {
@ -824,7 +900,7 @@ Pool.prototype._handleBlock = co(function* handleBlock(peer, block) {
throw err;
if (err.reason === 'bad-prevblk') {
if (this.options.headers) {
if (this.headersFirst) {
peer.increaseBan(10);
throw err;
}
@ -862,6 +938,30 @@ Pool.prototype._handleBlock = co(function* handleBlock(peer, block) {
this.chain.height,
block.rhash());
}
if (!this.headersFirst)
return;
if (!isCheckpoint) {
yield this.resolveHeaders(peer);
return;
}
node = this.nextCheckpoint;
checkpoint = this.getNextCheckpoint(node.height);
if (checkpoint) {
this.nextCheckpoint = checkpoint;
peer.sendGetHeaders([node.hash], checkpoint.hash);
return;
}
this.headersFirst = false;
this.nextCheckpoint = null;
this.headerChain.reset();
this.headerNext = null;
peer.sendGetBlocks([hash], null);
});
/**
@ -964,16 +1064,18 @@ Pool.prototype.handleHeaders = co(function* handleHeaders(peer, headers) {
*/
Pool.prototype._handleHeaders = co(function* handleHeaders(peer, headers) {
var items = [];
var ret = new VerifyResult();
var i, header, hash, last;
var isCheckpoint = false;
var i, header, hash, height, last, node;
if (!this.options.headers)
if (!this.headersFirst)
return;
if (!this.syncing)
return;
if (!peer.isLoader())
return;
this.logger.debug(
'Received %s headers from peer (%s).',
headers.length,
@ -987,39 +1089,96 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, headers) {
for (i = 0; i < headers.length; i++) {
header = headers[i];
hash = header.hash('hex');
last = this.headerChain.tail;
if (last && header.prevBlock !== last) {
assert(last);
height = last.height + 1;
if (header.prevBlock !== last.hash) {
peer.increaseBan(100);
throw new Error('Bad header chain.');
}
if (!header.verify(ret)) {
peer.reject(header, 'invalid', ret.reason, 100);
if (!header.verify()) {
peer.increaseBan(100);
throw new Error('Invalid header.');
}
last = hash;
node = new BlockNode(hash, last.height + 1);
if (yield this.hasBlock(hash))
continue;
if (!this.headerNext)
this.headerNext = node;
items.push(hash);
if (node.height === this.nextCheckpoint.height) {
if (node.hash !== this.nextCheckpoint.hash) {
peer.increaseBan(100);
throw new Error('Bad checkpoint header.');
}
isCheckpoint = true;
}
this.headerChain.push(node);
}
// Request the hashes we just added.
this.getBlock(peer, items);
if (isCheckpoint) {
this.headerChain.shift();
yield this.resolveHeaders(peer);
return;
}
// Restart the getheaders process
// Technically `last` is not indexed yet so
// the locator hashes will not be entirely
// accurate. However, it shouldn't matter
// that much since FindForkInGlobalIndex
// simply tries to find the latest block in
// the peer's chain.
if (headers.length === 2000)
yield peer.getHeaders(last);
// Restart the getheaders process.
peer.sendGetHeaders([node.hash], this.nextCheckpoint.hash);
});
/**
* Request current header chain blocks.
* @param {Peer} peer
* @returns {Promise}
*/
Pool.prototype.resolveHeaders = co(function* resolveHeaders(peer) {
var items = [];
var node;
if (!this.headerNext)
return;
for (node = this.headerNext; node; node = node.next) {
this.headerNext = node.next;
if (yield this.hasBlock(node.hash))
continue;
items.push(node.hash);
}
// Checkpoints should never be
// spaced more than 50k blocks
// apart from each other.
assert(items.length <= 50000);
this.getBlock(peer, items);
});
/**
* Find the next highest checkpoint.
* @private
* @param {Number} height
* @returns {Object}
*/
Pool.prototype.getNextCheckpoint = function getNextCheckpoint(height) {
var i, next;
for (i = 0; i < this.checkpoints.length; i++) {
next = this.checkpoints[i];
if (next.height > height)
return next;
}
};
/**
* Handle `inv` packet from peer (containing only BLOCK types).
* Potentially request headers if headers mode is enabled.
@ -1061,14 +1220,8 @@ Pool.prototype._handleBlockInv = co(function* handleBlockInv(peer, hashes) {
return;
// Request headers instead.
if (this.options.headers) {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
yield peer.getHeaders(null, hash);
}
if (this.headersFirst)
return;
}
this.logger.debug(
'Received %s block hashes from peer (%s).',
@ -1908,8 +2061,6 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
if (options.noRelay != null) {
assert(typeof options.noRelay === 'boolean');
this.noRelay = options.noRelay;
} else {
this.noRelay = this.spv === true;
}
if (options.host != null) {
@ -2040,6 +2191,8 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
if (this.spv) {
this.requiredServices |= common.services.BLOOM;
this.services &= ~common.services.NETWORK;
this.useCheckpoints = true;
this.noRelay = true;
}
if (this.selfish)
@ -2394,6 +2547,21 @@ BroadcastItem.prototype.inspect = function inspect() {
+ '>';
};
/*
* Helpers
*/
function BlockNode(hash, height) {
this.hash = hash;
this.height = height;
this.prev = null;
this.next = null;
}
function sortNodes(a, b) {
return a.height - b.height;
}
/*
* Expose
*/

View File

@ -32,6 +32,7 @@ function Network(options) {
this.port = options.port;
this.alertKey = options.alertKey;
this.checkpoints = options.checkpoints;
this.lastCheckpoint = options.lastCheckpoint;
this.halvingInterval = options.halvingInterval;
this.genesis = options.genesis;
this.genesisBlock = options.genesisBlock;

View File

@ -114,7 +114,13 @@ main.checkpoints = {
420000: 'a1ff746b2d42b834cb7d6b8981b09c265c2cabc016e8cc020000000000000000'
};
main.checkpoints.lastHeight = 420000;
/**
* Last checkpoint height.
* @const {Number}
* @default
*/
main.lastCheckpoint = 420000;
/**
* @const {Number}
@ -528,7 +534,7 @@ testnet.checkpoints = {
900000: '9bd8ac418beeb1a2cf5d68c8b5c6ebaa947a5b766e5524898d6f350000000000'
};
testnet.checkpoints.lastHeight = 900000;
testnet.lastCheckpoint = 900000;
testnet.halvingInterval = 210000;
@ -690,7 +696,7 @@ regtest.alertKey = new Buffer(
'hex');
regtest.checkpoints = {};
regtest.checkpoints.lastHeight = 0;
regtest.lastCheckpoint = 0;
regtest.halvingInterval = 150;
@ -845,7 +851,7 @@ segnet4.alertKey = new Buffer(
'hex');
segnet4.checkpoints = {};
segnet4.checkpoints.lastHeight = 0;
segnet4.lastCheckpoint = 0;
segnet4.halvingInterval = 210000;
@ -997,7 +1003,7 @@ simnet.alertKey = new Buffer(''
simnet.checkpoints = {};
simnet.checkpoints.lastHeight = 0;
simnet.lastCheckpoint = 0;
simnet.halvingInterval = 210000;

View File

@ -1936,7 +1936,7 @@ WalletDB.prototype._addBlock = co(function* addBlock(entry, txs) {
yield this.syncState(tip);
if (this.options.useCheckpoints) {
if (tip.height <= this.network.checkpoints.lastHeight)
if (tip.height <= this.network.lastCheckpoint)
return total;
}