diff --git a/lib/net/bip152.js b/lib/net/bip152.js index c72f576d..9d793fb8 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -93,7 +93,6 @@ CompactBlock.prototype.fromOptions = function fromOptions(options) { this.sipKey = options.sipKey; this.initKey(); - this.init(); return this; }; @@ -159,8 +158,6 @@ CompactBlock.prototype.fromRaw = function fromRaw(data) { this.ptx.push(new PrefilledTX(index, tx)); } - this.init(); - return this; }; @@ -457,7 +454,8 @@ CompactBlock.prototype.init = function init() { id = this.ids[i]; // Fails on siphash collision - assert(!this.idMap[id], 'Siphash collision.'); + if (this.idMap[id]) + return false; this.idMap[id] = i + offset; @@ -466,6 +464,8 @@ CompactBlock.prototype.init = function init() { // don't have lowlevel access to our hash // table. Hopefully we don't get hashdos'd. } + + return true; }; /** diff --git a/lib/net/peer.js b/lib/net/peer.js index f3796343..6391474b 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -956,19 +956,8 @@ Peer.prototype.sendHeaders = function sendHeaders(items) { Peer.prototype.sendCompactBlock = function sendCompactBlock(block) { var witness = this.compactWitness; - - // Try again with a new nonce - // if we get a siphash collision. - for (;;) { - try { - block = BIP152.CompactBlock.fromBlock(block, witness); - } catch (e) { - continue; - } - break; - } - - this.send(new packets.CmpctBlockPacket(block, witness)); + var compact = BIP152.CompactBlock.fromBlock(block, witness); + this.send(new packets.CmpctBlockPacket(compact, witness)); }; /** @@ -1485,8 +1474,11 @@ Peer.prototype.blockType = function blockType() { if (this.options.spv) return invTypes.FILTERED_BLOCK; - if (this.options.compact && this.hasCompact()) + if (this.options.compact + && this.hasCompactSupport() + && this.hasCompact()) { return invTypes.CMPCT_BLOCK; + } if (this.hasWitness()) return invTypes.WITNESS_BLOCK; @@ -1554,6 +1546,22 @@ Peer.prototype.getTX = function getTX(hashes) { this.getItems(this.txType(), hashes); }; +/** + * Send `getdata` to peer for a single block. + * @param {Hash} hash + */ + +Peer.prototype.getFullBlock = function getFullBlock(hash) { + var type = invTypes.BLOCK; + + assert(!this.options.spv); + + if (this.hasWitness()) + type |= InvItem.WITNESS_FLAG; + + this.getItems(type, [hash]); +}; + /** * Handle a packet payload. * @method @@ -1937,8 +1945,8 @@ Peer.prototype.handleSendCmpct = co(function* handleSendCmpct(packet) { } this.logger.info( - 'Peer initialized compact blocks (%s).', - this.hostname()); + 'Peer initialized compact blocks (mode=%d, version=%d) (%s).', + packet.mode, packet.version, this.hostname()); this.compactMode = packet.mode; this.compactWitness = packet.version === 2; @@ -2139,16 +2147,23 @@ Peer.prototype.sendReject = function sendReject(code, reason, msg) { */ Peer.prototype.sendCompact = function sendCompact(mode) { - this.logger.info('Initializing compact blocks (%s).', this.hostname()); - if (this.services & common.services.WITNESS) { if (this.version >= common.COMPACT_WITNESS_VERSION) { + this.logger.info( + 'Initializing witness compact blocks (%s).', + this.hostname()); this.send(new packets.SendCmpctPacket(mode, 2)); return; } } - this.send(new packets.SendCmpctPacket(mode, 1)); + if (this.version >= common.COMPACT_VERSION) { + this.logger.info( + 'Initializing normal compact blocks (%s).', + this.hostname()); + + this.send(new packets.SendCmpctPacket(mode, 1)); + } }; /** diff --git a/lib/net/pool.js b/lib/net/pool.js index c80e1469..98533155 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -1281,10 +1281,8 @@ Pool.prototype.handleOpen = co(function* handleOpen(peer) { } // We want compact blocks! - if (this.options.compact) { - if (peer.hasCompactSupport()) - peer.sendCompact(this.options.blockMode); - } + if (this.options.compact) + peer.sendCompact(this.options.blockMode); // Find some more peers. if (!this.hosts.isFull()) @@ -1710,6 +1708,7 @@ Pool.prototype.handleGetData = co(function* handleGetData(peer, packet) { var notFound = []; var txs = 0; var blocks = 0; + var compact = 0; var unknown = -1; var i, j, item, tx, block, result, height; @@ -1817,6 +1816,7 @@ Pool.prototype.handleGetData = co(function* handleGetData(peer, packet) { peer.sendCompactBlock(block); blocks++; + compact++; break; default: @@ -1847,8 +1847,8 @@ Pool.prototype.handleGetData = co(function* handleGetData(peer, packet) { if (blocks > 0) { this.logger.debug( - 'Served %d blocks with getdata (notfound=%d) (%s).', - blocks, notFound.length, peer.hostname()); + 'Served %d blocks with getdata (notfound=%d, cmpct=%d) (%s).', + blocks, notFound.length, compact, peer.hostname()); } if (unknown !== -1) { @@ -2665,7 +2665,7 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) { return; } - if (!peer.hasCompactSupport() && !peer.hasCompact()) { + if (!peer.hasCompactSupport() || !peer.hasCompact()) { this.logger.info( 'Peer sent unsolicited cmpctblock (%s).', peer.hostname()); @@ -2713,6 +2713,25 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) { return; } + try { + result = block.init(); + } catch (e) { + this.logger.debug( + 'Peer sent an invalid compact block (%s).', + peer.hostname()); + peer.increaseBan(100); + return; + } + + if (!result) { + this.logger.warning( + 'Siphash collision for %s. Requesting full block (%s).', + block.rhash(), peer.hostname()); + peer.getFullBlock(hash); + peer.increaseBan(10); + return; + } + result = block.fillMempool(witness, this.mempool); if (result) { @@ -2785,6 +2804,11 @@ Pool.prototype.handleGetBlockTxn = co(function* handleGetBlockTxn(peer, packet) return; } + this.logger.debug( + 'Sending blocktxn for %s to peer (%s).', + block.rhash(), + peer.hostname()); + res = BIP152.TXResponse.fromBlock(block, req); peer.send(new packets.BlockTxnPacket(res, peer.compactWitness)); @@ -2816,10 +2840,12 @@ Pool.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) { this.compactBlocks.remove(res.hash); if (!block.fillMissing(res)) { - peer.increaseBan(100); this.logger.warning( - 'Peer sent non-full blocktxn (%s).', + 'Peer sent non-full blocktxn for %s. Requesting full block (%s).', + block.rhash(), peer.hostname()); + peer.getFullBlock(res.hash); + peer.increaseBan(10); return; } @@ -3490,7 +3516,7 @@ Pool.prototype.getIP = co(function* getIP() { method: 'GET', uri: 'http://icanhazip.com', expect: 'txt', - timeout: 3000 + timeout: 2000 }); } catch (e) { return yield this.getIP2(); @@ -3523,7 +3549,7 @@ Pool.prototype.getIP2 = co(function* getIP2() { method: 'GET', uri: 'http://checkip.dyndns.org', expect: 'html', - timeout: 3000 + timeout: 2000 }); match = /IP Address:\s*([0-9a-f.:]+)/i.exec(res.body); diff --git a/test/block-test.js b/test/block-test.js index 41a8a0d2..8f7d4b95 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -297,6 +297,8 @@ describe('Block', function() { var map = {}; var i, tx, mempool, result; + assert(cblock1.init()); + assert.equal(cblock1.toRaw().toString('hex'), cmpct1[0]); assert.equal(cblock2.toRaw().toString('hex'), cmpct1[0]); @@ -334,6 +336,8 @@ describe('Block', function() { var map = {}; var i, tx, mid, keys, mempool, result, req, res; + assert(cblock1.init()); + assert.equal(cblock1.toRaw().toString('hex'), cmpct1[0]); assert.equal(cblock2.toRaw().toString('hex'), cmpct1[0]); @@ -388,6 +392,8 @@ describe('Block', function() { var map = {}; var i, tx, result, mempool; + assert(cblock1.init()); + assert.equal(cblock1.toRaw().toString('hex'), cmpct2); assert.equal(cblock2.toRaw().toString('hex'), cmpct2); @@ -423,6 +429,8 @@ describe('Block', function() { var map = {}; var i, tx, mid, keys, mempool, result, req, res; + assert(cblock1.init()); + assert.equal(cblock1.toRaw().toString('hex'), cmpct2); assert.equal(cblock2.toRaw().toString('hex'), cmpct2);