net: better headers first.
This commit is contained in:
parent
6bc016f313
commit
66ec0ae36e
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
236
lib/net/pool.js
236
lib/net/pool.js
@ -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
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user