pool: refactor header sync.

This commit is contained in:
Christopher Jeffrey 2017-01-21 16:07:02 -08:00
parent 219671c620
commit 6e13fe57c0
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 96 additions and 80 deletions

View File

@ -1012,7 +1012,7 @@ Chain.prototype._reset = co(function* reset(block, silent) {
// Reset state. // Reset state.
this.tip = tip; this.tip = tip;
this.height = tip.height; this.height = tip.height;
this.synced = this.isFull(true); this.synced = this.isFull();
state = yield this.getDeploymentState(); state = yield this.getDeploymentState();
@ -1669,8 +1669,8 @@ Chain.prototype.getCoinView = co(function* getCoinView(tx) {
* @returns {Boolean} * @returns {Boolean}
*/ */
Chain.prototype.isFull = function isFull(force) { Chain.prototype.isFull = function isFull() {
return !this.isInitial(force); return !this.isInitial();
}; };
/** /**
@ -1681,10 +1681,7 @@ Chain.prototype.isFull = function isFull(force) {
* @returns {Boolean} * @returns {Boolean}
*/ */
Chain.prototype.isInitial = function isInitial(force) { Chain.prototype.isInitial = function isInitial() {
if (!force && this.synced)
return false;
if (this.options.useCheckpoints) { if (this.options.useCheckpoints) {
if (this.height < this.network.lastCheckpoint) if (this.height < this.network.lastCheckpoint)
return true; return true;
@ -1700,7 +1697,7 @@ Chain.prototype.isInitial = function isInitial(force) {
}; };
/** /**
* Potentially emit a `sync` event. * Potentially emit a `full` event.
* @private * @private
*/ */
@ -1711,6 +1708,20 @@ Chain.prototype.maybeSync = function maybeSync() {
} }
}; };
/**
* Test the chain to see if it has the
* minimum required chainwork for the
* network.
* @returns {Boolean}
*/
Chain.prototype.hasChainwork = function hasChainwork() {
if (this.options.useCheckpoints)
return this.height >= this.network.lastCheckpoint;
return this.tip.chainwork.cmp(this.network.pow.chainwork) >= 0;
};
/** /**
* Get the fill percentage. * Get the fill percentage.
* @returns {Number} percent - Ranges from 0.0 to 1.0. * @returns {Number} percent - Ranges from 0.0 to 1.0.

View File

@ -1571,7 +1571,7 @@ RPC.prototype.getblocktemplate = co(function* getblocktemplate(args) {
if (this.pool.peers.size() === 0) if (this.pool.peers.size() === 0)
throw new RPCError('Bitcoin is not connected!'); throw new RPCError('Bitcoin is not connected!');
if (!this.chain.isFull()) if (!this.chain.synced)
throw new RPCError('Bitcoin is downloading blocks...'); throw new RPCError('Bitcoin is downloading blocks...');
} }

View File

@ -164,7 +164,7 @@ Mempool.prototype._addBlock = function addBlock(block, txs) {
} }
if (this.fees) if (this.fees)
this.fees.processBlock(block.height, entries, this.chain.isFull()); this.fees.processBlock(block.height, entries, this.chain.synced);
// We need to reset the rejects filter periodically. // We need to reset the rejects filter periodically.
// There may be a locktime in a TX that is now valid. // There may be a locktime in a TX that is now valid.
@ -972,7 +972,7 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) {
this.emit('add entry', entry); this.emit('add entry', entry);
if (this.fees) if (this.fees)
this.fees.processTX(entry, this.chain.isFull()); this.fees.processTX(entry, this.chain.synced);
this.logger.debug('Added tx %s to mempool.', tx.txid()); this.logger.debug('Added tx %s to mempool.', tx.txid());

View File

@ -232,7 +232,6 @@ Pool.prototype._open = co(function* _open() {
Pool.prototype.resetChain = function resetChain() { Pool.prototype.resetChain = function resetChain() {
var tip = this.chain.tip; var tip = this.chain.tip;
var watermark;
if (!this.options.headers) if (!this.options.headers)
return; return;
@ -242,16 +241,14 @@ Pool.prototype.resetChain = function resetChain() {
this.headerChain.reset(); this.headerChain.reset();
this.headerNext = null; this.headerNext = null;
watermark = this.getNextTip(tip.height); if (!this.chain.hasChainwork()) {
if (watermark) {
this.headersFirst = true; this.headersFirst = true;
this.headerTip = watermark; this.headerTip = this.getNextTip(tip.height);
this.headerChain.push(new BlockNode(tip.hash, tip.height)); this.headerChain.push(new HeaderEntry(tip.hash, tip.height));
this.logger.info( this.logger.info(
'Initialized header chain to height %d (watermark=%d).', 'Initialized header chain to height %d (watermark=%d).',
tip.height, tip.height,
watermark.height); this.headerTip.height);
} }
}; };
@ -665,33 +662,25 @@ Pool.prototype.sendGetAddr = function sendGetAddr() {
/** /**
* Request current header chain blocks. * Request current header chain blocks.
* @private
* @param {Peer} peer * @param {Peer} peer
* @returns {Promise}
*/ */
Pool.prototype.requestHeaders = co(function* requestHeaders(peer) { Pool.prototype.resolveHeaders = function resolveHeaders(peer) {
var items = []; var items = [];
var node; var node;
if (!this.headerNext)
return;
for (node = this.headerNext; node; node = node.next) { for (node = this.headerNext; node; node = node.next) {
this.headerNext = node.next; this.headerNext = node.next;
if (yield this.hasBlock(node.hash))
continue;
items.push(node.hash); items.push(node.hash);
if (items.length === 50000)
break;
} }
// Checkpoints should never be
// spaced more than 50k blocks
// apart from each other.
assert(items.length <= 50000);
this.getBlock(peer, items); this.getBlock(peer, items);
}); };
/** /**
* Find the next highest watermark block. * Find the next highest watermark block.
@ -707,15 +696,15 @@ Pool.prototype.getNextTip = function getNextTip(height) {
for (i = 0; i < this.network.checkpoints.length; i++) { for (i = 0; i < this.network.checkpoints.length; i++) {
next = this.network.checkpoints[i]; next = this.network.checkpoints[i];
if (next.height > height) if (next.height > height)
return next; return new HeaderEntry(next.hash, next.height);
} }
return; throw new Error('Could not find next tip.');
} }
if (this.chain.isFull()) if (this.chain.hasChainwork())
return; throw new Error('Could not find next tip.');
return new BlockNode(null, height + 20000); return new HeaderEntry(null, height + 20000);
}; };
/** /**
@ -1788,15 +1777,6 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) {
return; return;
} }
if (this.headerChain.size > 40000) {
this.logger.warning(
'Peer is sending too many headers (%s).',
peer.hostname());
peer.increaseBan(100);
peer.destroy();
return;
}
assert(this.headerChain.size > 0); assert(this.headerChain.size > 0);
for (i = 0; i < headers.length; i++) { for (i = 0; i < headers.length; i++) {
@ -1822,10 +1802,7 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) {
return; return;
} }
node = new BlockNode(hash, height); node = new HeaderEntry(hash, height);
if (!this.headerNext)
this.headerNext = node;
if (node.height === this.headerTip.height) { if (node.height === this.headerTip.height) {
if (this.options.useCheckpoints) { if (this.options.useCheckpoints) {
@ -1841,6 +1818,9 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) {
isWatermark = true; isWatermark = true;
} }
if (!this.headerNext)
this.headerNext = node;
this.headerChain.push(node); this.headerChain.push(node);
} }
@ -1849,14 +1829,14 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) {
headers.length, headers.length,
peer.hostname()); peer.hostname());
// Request the hashes we just added. // Request the blocks we just added.
if (isWatermark) { if (isWatermark) {
this.headerChain.shift(); this.headerChain.shift();
yield this.requestHeaders(peer); this.resolveHeaders(peer);
return; return;
} }
// Restart the getheaders process. // Request more headers.
peer.sendGetHeaders([node.hash], this.headerTip.hash); peer.sendGetHeaders([node.hash], this.headerTip.hash);
}); });
@ -1919,8 +1899,6 @@ Pool.prototype.addBlock = co(function* addBlock(peer, block) {
Pool.prototype._addBlock = co(function* addBlock(peer, block) { Pool.prototype._addBlock = co(function* addBlock(peer, block) {
var hash = block.hash('hex'); var hash = block.hash('hex');
var isWatermark = false;
var node, watermark;
if (!this.syncing) if (!this.syncing)
return; return;
@ -1933,22 +1911,6 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) {
return; return;
} }
if (this.headersFirst) {
node = this.headerChain.head;
assert(node);
if (hash === node.hash) {
if (node.height === this.headerTip.height) {
this.logger.info(
'Received checkpoint block %s (%d).',
block.rhash(), node.height);
isWatermark = true;
} else {
this.headerChain.shift();
}
}
}
peer.blockTime = util.ms(); peer.blockTime = util.ms();
try { try {
@ -1974,23 +1936,66 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) {
this.logStatus(block); this.logStatus(block);
yield this.resolveChain(peer, hash);
});
/**
* Resolve header chain.
* @private
* @param {Peer} peer
* @param {Hash} hash
* @returns {Promise}
*/
Pool.prototype.resolveChain = co(function* resolveChain(peer, hash) {
var node = this.headerChain.head;
if (!this.headersFirst) if (!this.headersFirst)
return; return;
if (!isWatermark && !this.chain.isFull()) { if (!peer.loader)
yield this.requestHeaders(peer); return;
if (peer.destroyed)
throw new Error('Peer was destroyed (header chain resolution).');
assert(node);
if (hash !== node.hash) {
this.logger.warning(
'Header hash mismatch %s != %s (%s).',
util.revHex(hash),
util.revHex(node.hash),
peer.hostname());
peer.destroy();
return; return;
} }
node = this.headerTip; if (!this.chain.hasChainwork()) {
watermark = this.getNextTip(node.height); if (node.height === this.headerTip.height) {
this.logger.info(
'Hit high watermark %s (%d).',
util.revHex(node.hash), node.height);
this.headerTip = this.getNextTip(node.height);
peer.sendGetHeaders([hash], this.headerTip.hash);
return;
}
this.headerChain.shift();
this.resolveHeaders(peer);
if (watermark) {
this.headerTip = watermark;
peer.sendGetHeaders([hash], watermark.hash);
return; return;
} }
this.logger.info(
'Chain hit target chainwork of %s. Switching to getblocks.',
this.chain.tip.chainwork.toString('hex', 64));
this.headersFirst = false; this.headersFirst = false;
this.headerTip = null; this.headerTip = null;
this.headerChain.reset(); this.headerChain.reset();
@ -3871,11 +3876,11 @@ NonceList.prototype.remove = function remove(hostname) {
}; };
/** /**
* BlockNode * HeaderEntry
* @constructor * @constructor
*/ */
function BlockNode(hash, height) { function HeaderEntry(hash, height) {
this.hash = hash; this.hash = hash;
this.height = height; this.height = height;
this.prev = null; this.prev = null;