diff --git a/lib/net/packets.js b/lib/net/packets.js index ac2c0447..c760a18e 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -1138,6 +1138,8 @@ HeadersPacket.prototype.getSize = function getSize() { HeadersPacket.prototype.toWriter = function toWriter(bw) { var i, item; + assert(this.items.length <= 2000, 'Too many headers.'); + bw.writeVarint(this.items.length); for (i = 0; i < this.items.length; i++) { @@ -1168,6 +1170,8 @@ HeadersPacket.prototype.fromReader = function fromReader(br) { var count = br.readVarint(); var i; + assert(count <= 2000, 'Too many headers.'); + for (i = 0; i < count; i++) this.items.push(Headers.fromReader(br)); diff --git a/lib/net/peer.js b/lib/net/peer.js index 9ba17936..9da62fca 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -2186,7 +2186,7 @@ Peer.prototype.reject = function reject(msg, code, reason, score) { */ Peer.prototype.sync = co(function* sync() { - var locator, tip, checkpoint; + var locator, tip, watermark; if (!this.pool.syncing) return false; @@ -2220,8 +2220,8 @@ Peer.prototype.sync = co(function* sync() { if (this.pool.headersFirst) { tip = this.chain.tip; - checkpoint = this.pool.nextCheckpoint; - this.sendGetHeaders([tip.hash], checkpoint.hash); + watermark = this.pool.headerTip; + this.sendGetHeaders([tip.hash], watermark.hash); return true; } diff --git a/lib/net/pool.js b/lib/net/pool.js index 7483e36a..05f4f225 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -116,7 +116,7 @@ function Pool(options) { this.headersFirst = false; this.headerChain = new List(); this.headerNext = null; - this.nextCheckpoint = null; + this.headerTip = null; this.checkpoints = []; this.peers = new PeerList(); @@ -241,29 +241,26 @@ Pool.prototype._open = co(function* _open() { Pool.prototype.resetChain = function resetChain() { var tip = this.chain.tip; - var checkpoint; + var watermark; if (!this.options.headers) return; - if (!this.chain.options.useCheckpoints) - return; - this.headersFirst = false; - this.nextCheckpoint = null; + this.headerTip = null; this.headerChain.reset(); this.headerNext = null; - checkpoint = this.getNextCheckpoint(tip.height); + watermark = this.getNextTip(tip.height); - if (checkpoint) { + if (watermark) { this.headersFirst = true; - this.nextCheckpoint = checkpoint; + this.headerTip = watermark; this.headerChain.push(new BlockNode(tip.hash, tip.height)); this.logger.info( - 'Initialized header chain to height %d (checkpoint=%d).', + 'Initialized header chain to height %d (watermark=%d).', tip.height, - checkpoint.height); + watermark.height); } }; @@ -383,7 +380,7 @@ Pool.prototype._disconnect = co(function* disconnect() { } this.headersFirst = false; - this.nextCheckpoint = null; + this.headerTip = null; this.headerChain.reset(); this.headerNext = null; @@ -594,7 +591,7 @@ Pool.prototype.sendGetAddr = function sendGetAddr() { * @returns {Promise} */ -Pool.prototype.resolveHeaders = co(function* resolveHeaders(peer) { +Pool.prototype.requestHeaders = co(function* requestHeaders(peer) { var items = []; var node; @@ -619,15 +616,21 @@ Pool.prototype.resolveHeaders = co(function* resolveHeaders(peer) { }); /** - * Find the next highest checkpoint. + * Find the next highest watermark block. * @private * @param {Number} height * @returns {Object} */ -Pool.prototype.getNextCheckpoint = function getNextCheckpoint(height) { +Pool.prototype.getNextTip = function getNextTip(height) { var i, next; + if (!this.options.useCheckpoints) { + if (this.chain.isFull()) + return; + return new BlockNode(null, height + 20000); + } + for (i = 0; i < this.checkpoints.length; i++) { next = this.checkpoints[i]; if (next.height > height) @@ -1527,7 +1530,7 @@ Pool.prototype.handleHeaders = co(function* handleHeaders(peer, packet) { Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { var headers = packet.items; - var isCheckpoint = false; + var isWatermark = false; var i, header, hash, height, last, node; if (!this.headersFirst) @@ -1539,13 +1542,22 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { if (!peer.isLoader()) return; + if (headers.length === 0) + return; + if (headers.length > 2000) { - this.increaseBan(100); + peer.increaseBan(100); return; } - if (headers.length === 0) + 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); @@ -1556,13 +1568,16 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { height = last.height + 1; if (header.prevBlock !== last.hash) { - peer.increaseBan(100); - throw new Error('Bad header chain.'); + this.logger.warning('Peer sent a bad header chain (%s).', peer.hostname); + peer.destroy(); + return; } if (!header.verify()) { + this.logger.warning('Peer sent an invalid header (%s).', peer.hostname); peer.increaseBan(100); - throw new Error('Invalid header.'); + peer.destroy(); + return; } node = new BlockNode(hash, last.height + 1); @@ -1570,12 +1585,18 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { if (!this.headerNext) this.headerNext = node; - if (node.height === this.nextCheckpoint.height) { - if (node.hash !== this.nextCheckpoint.hash) { - peer.increaseBan(100); - throw new Error('Bad checkpoint header.'); + if (node.height === this.headerTip.height) { + if (this.options.useCheckpoints) { + if (node.hash !== this.headerTip.hash) { + this.logger.warning( + 'Peer sent an invalid checkpoint (%s).', + peer.hostname); + peer.increaseBan(100); + peer.destroy(); + return; + } } - isCheckpoint = true; + isWatermark = true; } this.headerChain.push(node); @@ -1589,14 +1610,14 @@ Pool.prototype._handleHeaders = co(function* handleHeaders(peer, packet) { this.emit('headers', packet, peer); // Request the hashes we just added. - if (isCheckpoint) { + if (isWatermark) { this.headerChain.shift(); - yield this.resolveHeaders(peer); + yield this.requestHeaders(peer); return; } // Restart the getheaders process. - peer.sendGetHeaders([node.hash], this.nextCheckpoint.hash); + peer.sendGetHeaders([node.hash], this.headerTip.hash); }); /** @@ -1658,8 +1679,8 @@ Pool.prototype.addBlock = co(function* addBlock(peer, block) { Pool.prototype._addBlock = co(function* addBlock(peer, block) { var hash = block.hash('hex'); - var isCheckpoint = false; - var node, checkpoint; + var isWatermark = false; + var node, watermark; if (!this.syncing) return; @@ -1677,11 +1698,11 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) { assert(node); if (hash === node.hash) { - if (hash === this.nextCheckpoint.hash) { + if (node.height === this.headerTip.height) { this.logger.info( 'Received checkpoint block %s (%d).', hash, node.height); - isCheckpoint = true; + isWatermark = true; } else { this.headerChain.shift(); } @@ -1716,26 +1737,26 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) { if (!this.headersFirst) return; - if (!isCheckpoint) { - yield this.resolveHeaders(peer); + if (!isWatermark && !this.chain.isFull()) { + yield this.requestHeaders(peer); return; } - node = this.nextCheckpoint; - checkpoint = this.getNextCheckpoint(node.height); + node = this.headerTip; + watermark = this.getNextTip(node.height); - if (checkpoint) { - this.nextCheckpoint = checkpoint; - peer.sendGetHeaders([node.hash], checkpoint.hash); + if (watermark) { + this.headerTip = watermark; + peer.sendGetHeaders([hash], watermark.hash); return; } this.headersFirst = false; - this.nextCheckpoint = null; + this.headerTip = null; this.headerChain.reset(); this.headerNext = null; - peer.sendGetBlocks([hash], null); + yield this.getBlocks(peer, hash); }); /** @@ -2884,6 +2905,7 @@ function PoolOptions(options) { this.mempool = null; this.witness = this.network.witness; + this.useCheckpoints = false; this.spv = false; this.listen = false; this.headers = false; @@ -2956,6 +2978,14 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) { this.witness = this.chain.options.witness; } + if (options.useCheckpoints != null) { + assert(typeof options.useCheckpoints === 'boolean'); + assert(options.useCheckpoints === this.chain.options.useCheckpoints); + this.useCheckpoints = options.useCheckpoints; + } else { + this.useCheckpoints = this.chain.options.useCheckpoints; + } + if (options.spv != null) { assert(typeof options.spv === 'boolean'); assert(options.spv === this.chain.options.spv);