diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 9025f4e9..c82c23b3 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -1231,8 +1231,10 @@ Chain.prototype.add = function add(initial, peer, callback, force) { self.total += total; // Take heap snapshot for debugging. - if (self.total % 20 === 0) + if (self.total % 20 === 0) { bcoin.profiler.snapshot(); + utils.gc(); + } utils.nextTick(function() { if (self.isFull()) diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 724131fd..7e18acb3 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -411,6 +411,9 @@ Peer.prototype._onPacket = function onPacket(packet) { if (cmd === 'reject') return this._handleReject(payload); + if (cmd === 'alert') + return this._handleAlert(payload); + if (cmd === 'block') { payload = bcoin.compactblock(payload); } else if (cmd === 'merkleblock') { @@ -589,29 +592,23 @@ Peer.prototype._handleInv = function handleInv(items) { this.emit('inv', items); - // Always request advertised TXs txs = items.filter(function(item) { return item.type === 'tx'; - }); - - // Emit new blocks to schedule them between multiple peers - blocks = items.filter(function(item) { - return item.type === 'block'; - }, this).map(function(item) { + }).map(function(item) { return item.hash; }); - if (blocks.length === 1) - this.bestHash = utils.toHex(blocks[0]); + blocks = items.filter(function(item) { + return item.type === 'block'; + }).map(function(item) { + return item.hash; + }); - this.emit('blocks', blocks); + if (blocks.length > 0) + this.emit('blocks', blocks); - if (txs.length === 0) - return; - - this.emit('txs', txs.map(function(tx) { - return tx.hash; - })); + if (txs.length > 0) + this.emit('txs', txs); }; Peer.prototype._handleHeaders = function handleHeaders(headers) { @@ -634,14 +631,20 @@ Peer.prototype._handleReject = function handleReject(payload) { entry.e.emit('reject', payload); }; +Peer.prototype._handleAlert = function handleAlert(payload) { + this.emit('alert', payload); +}; + Peer.prototype.getHeaders = function getHeaders(hashes, stop) { utils.debug( 'Requesting headers packet from %s with getheaders', this.host); + utils.debug('Height: %s, Hash: %s, Stop: %s', this.pool.chain.getHeight(hashes[0]), hashes ? utils.revHex(hashes[0]) : 0, stop ? utils.revHex(stop) : 0); + this._write(this.framer.getHeaders(hashes, stop)); }; @@ -649,10 +652,12 @@ Peer.prototype.getBlocks = function getBlocks(hashes, stop) { utils.debug( 'Requesting inv packet from %s with getblocks', this.host); + utils.debug('Height: %s, Hash: %s, Stop: %s', this.pool.chain.getHeight(hashes[0]), hashes ? utils.revHex(hashes[0]) : 0, stop ? utils.revHex(stop) : 0); + this._write(this.framer.getBlocks(hashes, stop)); }; @@ -660,6 +665,7 @@ Peer.prototype.getMempool = function getMempool() { utils.debug( 'Requesting inv packet from %s with mempool', this.host); + this._write(this.framer.mempool()); }; @@ -679,8 +685,8 @@ Peer.prototype.setMisbehavior = function setMisbehavior(dos) { return this.pool.setMisbehavior(this, dos); }; -Peer.prototype.sendReject = function sendReject(obj, code, reason, dos) { - return this.pool.reject(this, obj, code, reason, dos); +Peer.prototype.sendReject = function sendReject(obj, code, reason, score) { + return this.pool.reject(this, obj, code, reason, score); }; /** diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 4e34a1e3..b59e1a72 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -804,6 +804,12 @@ Pool.prototype._createPeer = function _createPeer(options) { self.emit('reject', payload, peer); }); + peer.on('alert', function(payload) { + utils.debug('Received alert from: %s', peer.host); + utils.debug(payload); + self.emit('alert', payload, peer); + }); + peer.on('notfound', function(items) { items.forEach(function(item) { var req = self.request.map[utils.toHex(item.hash)]; @@ -1850,8 +1856,8 @@ Pool.prototype.removeSeed = function removeSeed(seed) { return true; }; -Pool.prototype.setMisbehavior = function setMisbehavior(peer, dos) { - peer.banScore += dos; +Pool.prototype.setMisbehavior = function setMisbehavior(peer, score) { + peer.banScore += score; if (peer.banScore >= constants.banScore) { this.peers.misbehaving[peer.host] = utils.now(); @@ -1885,9 +1891,9 @@ Pool.prototype.isMisbehaving = function isMisbehaving(host) { return false; }; -Pool.prototype.reject = function reject(peer, obj, code, reason, dos) { - if (dos != null) - peer.setMisbehavior(dos); +Pool.prototype.reject = function reject(peer, obj, code, reason, score) { + if (score != null) + peer.setMisbehavior(score); utils.debug('Rejecting %s %s: ccode=%s reason=%s', obj.type, obj.hash('hex'), code, reason); diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index adade16b..2a214304 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -624,6 +624,49 @@ Framer.mempool = function mempool() { return new Buffer([]); }; +Framer.alert = function alert(data, writer) { + var p, i, payload; + + if (!data.payload) { + p = new BufferWriter(); + p.write32(data.version); + p.write64(data.relayUntil); + p.write64(data.expiration); + p.write32(data.id); + p.write32(data.cancel); + p.writeVarint(data.cancels.length); + for (i = 0; i < data.cancels.length; i++) + p.write32(data.cancels[i]); + p.write32(data.minVer); + p.write32(data.maxVer); + p.writeVarint(data.subVers.length); + for (i = 0; i < data.subVers.length; i++) + p.writeVarString(data.subVers[i], 'ascii'); + p.write32(data.priority); + p.writeVarString(data.comment, 'ascii'); + p.writeVarString(data.statusBar, 'ascii'); + p.writeVarString('', 'ascii'); + payload = p.render(); + } else { + payload = data.payload; + } + + p = new BufferWriter(writer); + p.writeVarBytes(payload); + + if (data.signature) + p.writeVarBytes(data.signature); + else if (data.key) + p.writeVarBytes(bcoin.ec.sign(payload, data.key)); + else + assert(false, 'No key or signature.'); + + if (!writer) + p = p.render(); + + return p; +}; + /** * Expose */ diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index b9690a59..751b184e 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -26,6 +26,7 @@ function Parser() { this.pendingTotal = 0; this.waiting = 24; this.packet = null; + this.version = constants.minVersion; } utils.inherits(Parser, EventEmitter); @@ -64,6 +65,35 @@ Parser.prototype.feed = function feed(data) { } }; +Parser.prototype._feed = function feed(data) { + var chunk; + + while (data) { + this.pendingTotal += data.length; + this.pending.push(data); + + if (this.pendingTotal < this.waiting) + return; + + if (this.pending.length === 1) + chunk = this.pending[0]; + else + chunk = Buffer.concat(this.pending); + + if (chunk.length > this.waiting) { + data = chunk.slice(this.waiting); + chunk = chunk.slice(0, this.waiting); + //chunk = utils.slice(chunk, 0, this.waiting); + } else { + data = null; + } + + this.pending.length = 0; + this.pendingTotal = 0; + this.parse(chunk); + } +}; + Parser.prototype.parse = function parse(chunk) { if (chunk.length > constants.maxMessage) return this._error('Packet too large: %dmb.', utils.mb(chunk.length)); @@ -149,6 +179,9 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) { if (cmd === 'pong') return Parser.parsePong(p); + if (cmd === 'alert') + return Parser.parseAlert(p); + return p; }; @@ -187,11 +220,10 @@ Parser.parseVersion = function parseVersion(p) { agent = p.readVarString('ascii'); height = p.read32(); - try { + if (p.left() > 0) relay = p.readU8() === 1; - } catch (e) { + else relay = true; - } try { ts = ts.toNumber(); @@ -209,6 +241,8 @@ Parser.parseVersion = function parseVersion(p) { assert(ts >= 0, 'Timestamp is negative.'); assert(height >= 0, 'Height is negative.'); + this.version = version; + return { version: version, services: services, @@ -322,8 +356,8 @@ Parser.parseBlock = function parseBlock(p) { p.start(); version = p.read32(); - prevBlock = p.readHash(); - merkleRoot = p.readHash(); + prevBlock = p.readHash('hex'); + merkleRoot = p.readHash('hex'); ts = p.readU32(); bits = p.readU32(); nonce = p.readU32(); @@ -358,8 +392,8 @@ Parser.parseBlockCompact = function parseBlockCompact(p) { p.start(); version = p.read32(); - prevBlock = p.readHash(); - merkleRoot = p.readHash(); + prevBlock = p.readHash('hex'); + merkleRoot = p.readHash('hex'); ts = p.readU32(); bits = p.readU32(); nonce = p.readU32(); @@ -404,7 +438,7 @@ Parser.parseInput = function parseInput(p) { p = new BufferReader(p); p.start(); - hash = p.readHash(); + hash = p.readHash('hex'); index = p.readU32(); script = new bcoin.script(p.readVarBytes()); sequence = p.readU32(); @@ -661,7 +695,7 @@ Parser.parseAddress = function parseAddress(p, full) { p = new BufferReader(p); p.start(); - if (full) + if (full && this.version >= 31402) ts = p.readU32(); else ts = 0; @@ -675,7 +709,7 @@ Parser.parseAddress = function parseAddress(p, full) { try { services = services.toNumber(); } catch (e) { - services = 0; + services = services.uand(utils.MAX_SAFE_BN).toNumber(); } return { @@ -713,6 +747,65 @@ Parser.parseMempool = function parseMempool(p) { return {}; }; +Parser.parseAlert = function parseAlert(p) { + var version, relayUntil, expiration, id, cancel; + var cancels, count, i, minVer, maxVer, subVers; + var priority, comment, statusBar, msg; + var payload, p2, size; + + p = new BufferReader(p); + p.start(); + + payload = p.readVarBytes(); + signature = p.readVarBytes(); + size = p.end(); + + assert( + bcoin.ec.verify(payload, signature, network.alertKey), + 'Alert does not verify.'); + + p = new BufferReader(payload); + p.start(); + version = p.read32(); + relayUntil = p.read64(); + expiration = p.read64(); + id = p.read32(); + cancel = p.read32(); + cancels = []; + count = p.readVarint(); + for (i = 0; i < count; i++) + cancels.push(p.read32()); + minVer = p.read32(); + maxVer = p.read32(); + subVers = []; + count = p.readVarint(); + for (i = 0; i < count; i++) + subVers.push(p.readVarString('ascii')); + priority = p.read32(); + comment = p.readVarString('ascii'); + statusBar = p.readVarString('ascii'); + p.readVarString('ascii'); + p.end(); + + return { + version: version, + relayUntil: relayUntil, + expiration: expiration, + id: id, + cancel: cancel, + cancels: cancels, + minVer: minVer, + maxVer: maxVer, + subVers: subVers, + priority: priority, + comment: comment, + statusBar: statusBar, + payload: payload, + signature: signature, + _size: size + }; +}; + /** * Expose */ diff --git a/lib/bcoin/reader.js b/lib/bcoin/reader.js index b55c785b..1b13eca0 100644 --- a/lib/bcoin/reader.js +++ b/lib/bcoin/reader.js @@ -54,6 +54,9 @@ BufferReader.prototype.endData = function endData() { if (size === data.length) return data; + // if (this.zeroCopy) + // return data.slice(start, end); + return utils.slice(data, start, end); }; @@ -258,6 +261,7 @@ BufferReader.prototype.getSize = function getSize() { }; BufferReader.prototype.seek = function seek(off) { + assert(this.offset + off >= 0); assert(this.offset + off <= this.data.length); this.offset += off; return off; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index dbac74d7..1a3b49b7 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -25,6 +25,11 @@ else utils.nop = function() {}; +utils.gc = !utils.isBrowser && typeof gc === 'function' ? gc : utils.nop; + +if (utils.gc !== utils.nop) + console.error('bcoin started with --expose-gc enabled.'); + utils.slice = function slice(buf, start, end) { var clone; @@ -974,6 +979,7 @@ utils.host = function host(addr) { return addr.split(':')[0]; }; +utils.U32 = new bn(0xffffffff); utils.U64 = new bn('ffffffffffffffff', 'hex'); utils.nonce = function nonce() { @@ -1209,6 +1215,7 @@ utils.writeU64NBE = function writeU64NBE(dst, num, off) { }; utils.MAX_SAFE_INTEGER = 0x1fffffffffffff; +utils.MAX_SAFE_BN = new bn(utils.MAX_SAFE_INTEGER); utils.MAX_SAFE_HI = 0x1fffff; utils.write64N = function write64N(dst, num, off) { diff --git a/lib/bcoin/writer.js b/lib/bcoin/writer.js index 73016f4f..4ca054bb 100644 --- a/lib/bcoin/writer.js +++ b/lib/bcoin/writer.js @@ -177,6 +177,7 @@ BufferWriter.prototype.writeVarint = function writeVarint(value) { }; BufferWriter.prototype.fill = function fill(value, size) { + assert(size >= 0); var buf = new Buffer(size); buf.fill(value); this.written += buf.length;