diff --git a/bin/node b/bin/node index 457a3200..6caa9647 100755 --- a/bin/node +++ b/bin/node @@ -17,6 +17,7 @@ var node = new bcoin.fullnode({ logFile: true, db: 'leveldb', prune: process.argv.indexOf('--prune') !== -1, + compact: process.argv.indexOf('--compact') !== -1, useCheckpoints: process.argv.indexOf('--checkpoints') !== -1, selfish: process.argv.indexOf('--selfish') !== -1, headers: process.argv.indexOf('--headers') !== -1, diff --git a/lib/bcoin/bip152.js b/lib/bcoin/bip152.js index 929e5291..786d2c11 100644 --- a/lib/bcoin/bip152.js +++ b/lib/bcoin/bip152.js @@ -39,6 +39,7 @@ function CompactBlock(options) { this.count = 0; this.k0 = null; this.k1 = null; + this.timeout = null; if (options) this.fromOptions(options); @@ -126,7 +127,7 @@ CompactBlock.fromRaw = function fromRaw(data, enc) { return new CompactBlock().fromRaw(data); }; -CompactBlock.prototype.toRaw = function toRaw(writer) { +CompactBlock.prototype.toRaw = function toRaw(witness, writer) { var p = bcoin.writer(writer); var i, id, lo, hi, ptx; @@ -155,7 +156,10 @@ CompactBlock.prototype.toRaw = function toRaw(writer) { for (i = 0; i < this.ptx.length; i++) { ptx = this.ptx[i]; p.writeVarint2(ptx[0]); - ptx[1].toRaw(p); + if (witness) + ptx[1].toRaw(p); + else + ptx[1].toNormal(p); } if (!writer) @@ -353,6 +357,18 @@ CompactBlock.fromBlock = function fromBlock(block, nonce) { return new CompactBlock().fromBlock(block, nonce); }; +CompactBlock.prototype.startTimeout = function startTimeout(time, callback) { + assert(this.timeout == null); + this.timeout = setTimeout(callback, time); +}; + +CompactBlock.prototype.stopTimeout = function stopTimeout() { + if (this.timeout != null) { + clearTimeout(this.timeout); + this.timeout = null; + } +}; + /** * Represents a BlockTransactionsRequest (bip152): `getblocktxn` packet. * @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki @@ -527,10 +543,10 @@ BlockTX.prototype.toRaw = function toRaw(witness, writer) { for (i = 0; i < this.txs.length; i++) { tx = this.txs[i]; - if (!witness) - tx.toNormal(p); - else + if (witness) tx.toRaw(p); + else + tx.toNormal(p); } if (!writer) diff --git a/lib/bcoin/env.js b/lib/bcoin/env.js index fc8cf02f..2d978f61 100644 --- a/lib/bcoin/env.js +++ b/lib/bcoin/env.js @@ -148,6 +148,8 @@ function Environment() { this.mtx = require('./mtx'); this.txdb = require('./txdb'); this.abstractblock = require('./abstractblock'); + this.bip151 = require('./bip151'); + this.bip152 = require('./bip152'); this.memblock = require('./memblock'); this.block = require('./block'); this.merkleblock = require('./merkleblock'); diff --git a/lib/bcoin/fullnode.js b/lib/bcoin/fullnode.js index 8c4410d0..df15283b 100644 --- a/lib/bcoin/fullnode.js +++ b/lib/bcoin/fullnode.js @@ -95,6 +95,7 @@ function Fullnode(options) { witness: this.network.witness, selfish: this.options.selfish, headers: this.options.headers, + compact: this.options.compact, proxyServer: this.options.proxyServer, preferredSeed: this.options.preferredSeed, spv: false diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 8030c7d2..552c8d75 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -1148,7 +1148,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx) { var prevout = {}; var i, hash, input, prev; - if (tx.getSize() > 5000) { + if (tx.getSize() > 99999) { this.logger.debug('Ignoring large orphan: %s', tx.rhash); this.emit('bad orphan', tx); return; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index f5c0854a..bed6b847 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -106,6 +106,9 @@ function Peer(pool, options) { this.syncSent = false; this.loader = options.loader || false; this._connectTimeout = null; + this.compactMode = null; + this.compactBlocks = {}; + this.sentAddr = false; this.challenge = null; this.lastPong = -1; @@ -266,6 +269,10 @@ Peer.prototype._onConnect = function _onConnect() { self.write(self.framer.haveWitness()); } + // We want compact blocks! + if (self.options.compact && self.version.hasCompact()) + self.sendCompact(); + // Find some more peers. self.write(self.framer.getAddr()); @@ -697,8 +704,17 @@ Peer.prototype.getData = function getData(items) { for (i = 0; i < items.length; i++) { item = items[i]; + if (item.toInv) item = item.toInv(); + + if (this.options.compact + && this.compactMode + && item.isBlock() + && !item.hasWitness()) { + item.type = constants.inv.CMPCT_BLOCK; + } + data[i] = item; } @@ -792,6 +808,18 @@ Peer.prototype._onPacket = function onPacket(packet) { case 'notfound': this.fire(cmd, payload); break; + case 'encinit': + return this._handleEncinit(payload); + case 'encack': + return this._handleEncack(payload); + case 'sendcmpct': + return this._handleSendCmpct(payload); + case 'cmpctblock': + return this._handleCmpctBlock(payload); + case 'getblocktxn': + return this._handleGetBlockTxn(payload); + case 'blocktxn': + return this._handleBlockTxn(payload); default: this.logger.warning('Unknown packet: %s.', cmd); this.fire(cmd, payload); @@ -1341,6 +1369,48 @@ Peer.prototype._handleMempool = function _handleMempool() { }); }; +/** + * Get a block/tx either from the broadcast map, mempool, or blockchain. + * @param {InvItem} item + * @param {Function} callback - Returns + * [Error, {@link Block}|{@link MempoolEntry}]. + */ + +Peer.prototype._getItem = function _getItem(item, callback) { + var entry = this.pool.inv.map[item.hash]; + + if (entry) { + this.logger.debug( + 'Peer requested %s %s as a %s packet (%s).', + entry.type === constants.inv.TX ? 'tx' : 'block', + utils.revHex(entry.hash), + item.hasWitness() ? 'witness' : 'normal', + this.hostname); + + entry.ack(peer); + + if (entry.msg) + return callback(null, entry.msg); + } + + if (this.options.selfish) + return callback(); + + if (item.isTX()) { + if (!this.mempool) + return callback(); + return this.mempool.getEntry(item.hash, callback); + } + + if (this.chain.db.options.spv) + return callback(); + + if (this.chain.db.options.prune) + return callback(); + + return this.chain.db.getBlock(item.hash, callback); +}; + /** * Handle `getdata` packet. * @private @@ -1349,9 +1419,7 @@ Peer.prototype._handleMempool = function _handleMempool() { Peer.prototype._handleGetData = function _handleGetData(items) { var self = this; - var check = []; - var notfound = []; - var i, item, entry, witness; + var notFound = []; var unlock = this.locker.lock(_handleGetData, [items]); if (!unlock) @@ -1370,68 +1438,31 @@ Peer.prototype._handleGetData = function _handleGetData(items) { return done(); } - // Hit the broadcast queue first. - for (i = 0; i < items.length; i++) { - item = items[i]; - entry = this.pool.inv.map[item.hash]; - witness = (item.type & constants.WITNESS_MASK) !== 0; + utils.forEachSerial(items, function(item, next) { + var i, tx, block; - // If the item isn't present, queue it up - // to be checked in the mempool or chain. - if (!entry) { - check.push(item); - continue; - } + self._getItem(item, function(err, entry) { + if (err) + return next(err); - if ((item.type & ~constants.WITNESS_MASK) !== entry.type) { - self.logger.debug( - 'Peer requested an existing item with the wrong type (%s).', - this.hostname); - continue; - } - - this.logger.debug( - 'Peer requested %s %s as a %s packet (%s).', - entry.type === constants.inv.TX ? 'tx' : 'block', - utils.revHex(entry.hash), - witness ? 'witness' : 'normal', - this.hostname); - - // Try to send the data. - // If the item was announce-only (i.e. the data - // isn't actually contained in the broadcast - // item), check this on the mempool/chain. - if (!entry.send(this, witness)) - check.push(item); - } - - if (this.pool.options.selfish) - return done(); - - // Item wasn't found in broadcast queue. - // Check the mempool and chain. - utils.forEachSerial(check, function(item, next) { - var type = item.type & ~constants.WITNESS_MASK; - var witness = (item.type & constants.WITNESS_MASK) !== 0; - var hash = item.hash; - var i, tx, data; - - if (type === constants.inv.TX) { - if (!self.mempool) { - notfound.push(new InvItem(constants.inv.TX, hash)); + if (!entry) { + notFound.push(item); return next(); } - return self.mempool.getEntry(hash, function(err, entry) { - if (err) - return next(err); - if (!entry) { - notfound.push(new InvItem(constants.inv.TX, hash)); + if (item.isTX()) { + tx = entry.tx; + + // Coinbases are an insta-ban from any node. + // This should technically never happen, but + // it's worth keeping here just in case. A + // 24-hour ban from any node is rough. + if (tx.isCoinbase()) { + notFound.push(item); + self.logger.warning('Failsafe: tried to relay a coinbase.'); return next(); } - tx = entry.tx; - // We should technically calculate this in // the `mempool` handler, but it would be // too slow. @@ -1440,105 +1471,93 @@ Peer.prototype._handleGetData = function _handleGetData(items) { return next(); } - data = witness - ? self.framer.witnessTX(tx) - : self.framer.tx(tx); + if (item.hasWitness()) + self.write(self.framer.witnessTX(tx)); + else + self.write(self.framer.tx(tx)); - self.write(data); - - next(); - }); - } - - if (type === constants.inv.BLOCK) { - if (self.chain.db.options.spv) { - notfound.push(new InvItem(constants.inv.BLOCK, hash)); return next(); } - if (self.chain.db.options.prune) { - notfound.push(new InvItem(constants.inv.BLOCK, hash)); - return next(); - } - return self.chain.db.getBlock(hash, function(err, block) { - if (err) - return next(err); - if (!block) { - notfound.push(new InvItem(constants.inv.BLOCK, hash)); + block = entry; + + switch (item.type) { + case constants.inv.BLOCK: + case constants.inv.WITNESS_BLOCK: + if (item.hasWitness()) + self.write(self.framer.witnessBlock(block)); + else + self.write(self.framer.block(block)); + break; + case constants.inv.FILTERED_BLOCK: + case constants.inv.WITNESS_FILTERED_BLOCK: + if (!self.spvFilter) { + notFound.push(item); + return next(); + } + + block = block.toMerkle(self.spvFilter); + + self.write(self.framer.merkleBlock(block)); + + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + + if (item.hasWitness()) + self.write(self.framer.witnessTX(tx)); + else + self.write(self.framer.tx(tx)); + } + + break; + case constants.inv.CMPCT_BLOCK: + // Fallback to full block. + if (block.height < self.chain.tip.height - 10) { + self.write(self.framer.block(block)); + break; + } + + // Try again with a new nonce + // if we get a siphash collision. + for (;;) { + try { + block = bcoin.bip152.CompactBlock.fromBlock(block); + } catch (e) { + continue; + } + break; + } + + self.write(self.framer.cmpctBlock(block)); + break; + default: + self.logger.warning( + 'Peer sent an unknown getdata type: %s (%s).', + item.type, + self.hostname); + notFound.push(item); return next(); - } - - data = witness - ? self.framer.witnessBlock(block) - : self.framer.block(block); - - self.write(data); - - if (hash === self.hashContinue) { - self.sendInv(new InvItem(constants.inv.BLOCK, self.chain.tip.hash)); - self.hashContinue = null; - } - - next(); - }); - } - - if (type === constants.inv.FILTERED_BLOCK) { - if (self.chain.db.options.spv) { - notfound.push(new InvItem(constants.inv.BLOCK, hash)); - return next(); } - if (self.chain.db.options.prune) { - notfound.push(new InvItem(constants.inv.BLOCK, hash)); - return next(); + + if (item.hash === self.hashContinue) { + self.sendInv(new InvItem(constants.inv.BLOCK, self.chain.tip.hash)); + self.hashContinue = null; } - return self.chain.db.getBlock(hash, function(err, block) { - if (err) - return next(err); - if (!block) { - notfound.push(new InvItem(constants.inv.BLOCK, hash)); - return next(); - } - - block = block.toMerkle(self.spvFilter); - - self.write(self.framer.merkleBlock(block)); - - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - - tx = witness - ? self.framer.witnessTX(tx) - : self.framer.tx(tx); - - self.write(tx); - } - - if (hash === self.hashContinue) { - self.sendInv(new InvItem(constants.inv.BLOCK, self.chain.tip.hash)); - self.hashContinue = null; - } - - next(); - }); - } - - notfound.push(new InvItem(type, hash)); - - return next(); + next(); + }); }, function(err) { if (err) - self.emit('error', err); + return done(err); self.logger.debug( 'Served %d items with getdata (notfound=%d) (%s).', - items.length - notfound.length, - notfound.length, + items.length - notFound.length, + notFound.length, self.hostname); - if (notfound.length > 0) - self.write(self.framer.notFound(notfound)); + if (notFound.length > 0) + self.write(self.framer.notFound(notFound)); done(); }); @@ -1628,6 +1647,13 @@ Peer.prototype._handleGetAddr = function _handleGetAddr() { if (this.pool.options.selfish) return; + if (this.sentAddr) { + this.logger.debug('Ignoring repeated getaddr (%s).', this.hostname); + return; + } + + this.sentAddr = true; + for (i = 0; i < this.pool.hosts.length; i++) { host = this.pool.hosts[i]; @@ -1746,6 +1772,188 @@ Peer.prototype._handleAlert = function _handleAlert(alert) { this.fire('alert', alert); }; +/** + * Handle `encinit` packet. + * @private + * @param {Object} + */ + +Peer.prototype._handleEncinit = function _handleEncinit(payload) { + this.fire('encinit', payload); +}; + +/** + * Handle `encack` packet. + * @private + * @param {Object} + */ + +Peer.prototype._handleEncack = function _handleEncack(payload) { + this.fire('encack', payload); +}; + +/** + * Handle `sendcmpct` packet. + * @private + * @param {Object} + */ + +Peer.prototype._handleSendCmpct = function _handleSendCmpct(payload) { + if (payload.version !== 1) { + // Ignore + return; + } + + if (payload.mode !== 0) { + // Ignore (we can't do mode 1 yet). + return; + } + + this.compactMode = payload; + this.fire('sendcmpct', payload); +}; + +/** + * Handle `cmpctblock` packet. + * @private + * @param {Object} + */ + +Peer.prototype._handleCmpctBlock = function _handleCmpctBlock(block) { + var self = this; + var hash = block.hash('hex'); + + function done(err) { + if (err) { + self.emit('error', err); + return; + } + self.fire('cmpctblock', block); + } + + if (!this.options.compact) { + this.logger.info('Peer sent unsolicited cmpctblock (%s).', this.hostname); + return; + } + + if (!this.mempool) { + this.logger.warning('Requesting compact blocks without a mempool!'); + return done(); + } + + if (this.compactBlocks[hash]) { + this.logger.debug( + 'Peer sent us a duplicate compact block (%s).', + this.hostname); + return done(); + } + + // Sort of a lock too. + this.compactBlocks[hash] = block; + + block.fillMempool(this.mempool, function(err, result) { + if (err) + return done(err); + + if (result) { + delete self.compactBlocks[hash]; + self.emit('block', block.toBlock()); + return; + } + + self.write(self.framer.getBlockTxn(block.toRequest())); + + block.startTimeout(10000, function() { + self.logger.debug( + 'Compact block timed out: %s (%s).', + block.rhash, self.hostname); + delete self.compactBlocks[hash]; + }); + }); +}; + +/** + * Handle `getblocktxn` packet. + * @private + * @param {Object} + */ + +Peer.prototype._handleGetBlockTxn = function _handleGetBlockTxn(payload) { + var self = this; + var blockTX; + + function done(err) { + if (err) { + self.emit('error', err); + return; + } + self.fire('blocktxn', payload); + } + + if (this.chain.db.options.spv) + return done(); + + if (this.chain.db.options.prune) + return done(); + + if (this.options.selfish) + return done(); + + this.chain.db.getBlock(payload.hash, function(err, block) { + if (err) + return done(err); + + if (!block) { + self.logger.info( + 'Peer sent getblocktxn for non-existent block (%s).', + self.hostname); + self.setMisbehavior(100); + return done(); + } + + if (block.height < self.chain.tip.height - 15) { + self.logger.info( + 'Peer sent a getblocktxn for a block > 15 deep (%s)', + self.hostname); + return done(); + } + + blockTX = bcoin.bip152.BlockTX.fromBlock(block, payload); + + self.write(self.framer.blockTxn(blockTX)); + + done(); + }); +}; + +/** + * Handle `blocktxn` packet. + * @private + * @param {Object} + */ + +Peer.prototype._handleBlockTxn = function _handleBlockTxn(payload) { + var block = this.compactBlocks[payload.hash]; + + if (!block) { + this.logger.info('Peer sent unsolicited blocktxn (%s).', this.hostname); + return; + } + + this.fire('getblocktxn', payload); + + block.stopTimeout(); + delete this.compactBlocks[payload.hash]; + + if (!block.fillMissing(payload.txs)) { + this.setMisbehavior(100); + this.logger.info('Peer sent non-full blocktxn (%s).', this.hostname); + return; + } + + this.emit('block', block.toBlock()); +}; + /** * Send an `alert` to peer. * @param {AlertPacket} alert @@ -1836,6 +2044,15 @@ Peer.prototype.sendReject = function sendReject(code, reason, obj) { this.write(this.framer.reject(reject)); }; +/** + * Send a `sendcmpct` packet. + */ + +Peer.prototype.sendCompact = function sendCompact() { + var cmpct = new bcoin.bip152.SendCompact(0, 1); + this.write(this.framer.sendCmpct(cmpct)); +}; + /** * Check whether the peer is misbehaving (banScore >= 100). * @returns {Boolean} diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 256a86e8..66e06340 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -572,7 +572,8 @@ Pool.prototype._addLoader = function _addLoader() { loader: true, network: true, spv: this.options.spv, - witness: this.options.witness + witness: this.options.witness, + compact: this.options.compact }); this.logger.info('Added loader peer (%s).', peer.hostname); @@ -973,7 +974,8 @@ Pool.prototype._createPeer = function _createPeer(options) { network: options.network, spv: options.spv, witness: options.witness, - headers: this.options.headers + headers: this.options.headers, + compact: this.options.compact }); peer.once('close', function() { @@ -1275,7 +1277,8 @@ Pool.prototype._addLeech = function _addLeech(socket) { socket: socket, network: false, spv: false, - witness: false + witness: false, + compact: false }); this.logger.info('Added leech peer (%s).', peer.hostname); @@ -1316,7 +1319,8 @@ Pool.prototype._addPeer = function _addPeer() { host: host, network: true, spv: this.options.spv, - witness: this.options.witness + witness: this.options.witness, + compact: this.options.compact }); this.peers.pending.push(peer); @@ -2046,7 +2050,7 @@ LoadRequest.prototype.destroy = function destroy() { */ LoadRequest.prototype._onTimeout = function _onTimeout() { - if (this.type === this.pool.block.type + if (this.type !== this.pool.tx.type && this.peer === this.pool.peers.load) { this.pool.logger.debug( 'Loader took too long serving a block. Finding a new one.'); @@ -2267,47 +2271,6 @@ BroadcastItem.prototype.finish = function finish(err) { this.callback.length = 0; }; -/** - * Send the item to a peer. - * @param {Peer} peer - * @param {Boolean} witness - Whether to use the witness serialization. - * @returns {Boolean} Whether the data is available to be sent. - */ - -BroadcastItem.prototype.send = function send(peer, witness) { - var data; - - if (!this.msg) { - this.ack(peer); - return false; - } - - if (this.type === constants.inv.TX) { - // Failsafe - we never want to relay coinbases. - // They are an insta-ban from any bitcoind node. - if (this.msg.isCoinbase()) { - peer.write(peer.framer.notFound([this.toInv()])); - this.pool.logger.warning('Failsafe: tried to relay a coinbase.'); - this.finish(new Error('Coinbase.')); - return true; - } - - data = witness - ? peer.framer.witnessTX(this.msg) - : peer.framer.tx(this.msg); - } else { - data = witness - ? peer.framer.witnessBlock(this.msg) - : peer.framer.block(this.msg); - } - - peer.write(data); - - this.ack(peer); - - return true; -}; - /** * Handle an ack from a peer. * @param {Peer} peer diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 6a20b94e..d21aee04 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -27,7 +27,7 @@ exports.MIN_VERSION = 70001; * @default */ -exports.VERSION = 70012; +exports.VERSION = 70014; /** * Max message size (~4mb with segwit, formerly 2mb) diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 22c4a08b..2e7dc7ef 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -404,6 +404,66 @@ Framer.prototype.feeFilter = function feeFilter(options) { return this.packet('feefilter', Framer.feeFilter(options)); }; +/** + * Create an encinit packet with a header. + * @param {Object} options - See {@link Framer.encinit}. + * @returns {Buffer} encinit packet. + */ + +Framer.prototype.encinit = function encinit(options) { + return this.packet('encinit', Framer.encinit(options)); +}; + +/** + * Create an encack packet with a header. + * @param {Object} options - See {@link Framer.encack}. + * @returns {Buffer} encack packet. + */ + +Framer.prototype.encack = function encack(options) { + return this.packet('encack', Framer.encack(options)); +}; + +/** + * Create a sendcmpct packet with a header. + * @param {Object} options - See {@link Framer.sendCmpct}. + * @returns {Buffer} sendCmpct packet. + */ + +Framer.prototype.sendCmpct = function sendCmpct(options) { + return this.packet('sendcmpct', Framer.sendCmpct(options)); +}; + +/** + * Create a cmpctblock packet with a header. + * @param {Object} options - See {@link Framer.cmpctBlock}. + * @returns {Buffer} cmpctBlock packet. + */ + +Framer.prototype.cmpctBlock = function cmpctBlock(options) { + return this.packet('cmpctblock', Framer.cmpctBlock(options)); +}; + +/** + * Create a getblocktxn packet with a header. + * @param {Object} options - See {@link Framer.getBlockTxn}. + * @returns {Buffer} getBlockTxn packet. + */ + +Framer.prototype.getBlockTxn = function getBlockTxn(options) { + return this.packet('getblocktxn', Framer.getBlockTxn(options)); +}; + +/** + * Create a blocktxn packet with a header. + * @param {Object} options - See {@link Framer.blockTxn}. + * @returns {Buffer} blockTxn packet. + */ + +Framer.prototype.blockTxn = function blockTxn(options) { + return this.packet('blocktxn', Framer.blockTxn(options)); +}; + /** * Create a version packet (without a header). * @param {VersionPacket} options @@ -868,6 +928,72 @@ Framer.feeFilter = function feeFilter(data, writer) { return p; }; +/** + * Create an encinit packet (without a header). + * @param {BIP151} data + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +Framer.encinit = function encinit(data, writer) { + return data.toEncinit(writer); +}; + +/** + * Create an encinit packet (without a header). + * @param {BIP151} data + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +Framer.encack = function encack(data, writer) { + return data.toEncack(writer); +}; + +/** + * Create a sendcmpct packet (without a header). + * @param {SendCompact} data + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +Framer.sendCmpct = function sendCmpct(data, writer) { + return data.toRaw(writer); +}; + +/** + * Create a cmpctblock packet (without a header). + * @param {CompactBlock} data + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +Framer.cmpctBlock = function cmpctBlock(data, writer) { + return data.toRaw(false, writer); +}; + +/** + * Create a getblocktxn packet (without a header). + * @param {BlockTXRequest} data + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +Framer.getBlockTxn = function getBlockTxn(data, writer) { + return data.toRaw(writer); +}; + +/** + * Create a blocktxn packet (without a header). + * @param {BlockTX} data + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +Framer.blockTxn = function blockTxn(data, writer) { + return data.toRaw(false, writer); +}; + /* * Expose */ diff --git a/lib/bcoin/protocol/packets.js b/lib/bcoin/protocol/packets.js index fc22610b..5273238a 100644 --- a/lib/bcoin/protocol/packets.js +++ b/lib/bcoin/protocol/packets.js @@ -176,6 +176,15 @@ VersionPacket.prototype.hasHeaders = function hasHeaders() { return this.version >= 31800; }; +/** + * Test whether the protocol version supports bip152. + * @returns {Boolean} + */ + +VersionPacket.prototype.hasCompact = function hasCompact() { + return this.version >= 70014; +}; + /** * Inject properties from serialized data. * @private @@ -285,12 +294,45 @@ InvItem.fromRaw = function fromRaw(data, enc) { return new InvItem().fromRaw(data); }; +/** + * Test whether the inv item is a block. + * @returns {Boolean} + */ + +InvItem.prototype.isBlock = function isBlock() { + switch (this.type) { + case constants.inv.BLOCK: + case constants.inv.WITNESS_BLOCK: + case constants.inv.FILTERED_BLOCK: + case constants.inv.WITNESS_FILTERED_BLOCK: + case constants.inv.CMPCT_BLOCK: + return true; + default: + return false; + } +}; + +/** + * Test whether the inv item is a tx. + * @returns {Boolean} + */ + +InvItem.prototype.isTX = function isTX() { + switch (this.type) { + case constants.inv.TX: + case constants.inv.WITNESS_TX: + return true; + default: + return false; + } +}; + /** * Test whether the inv item has the witness bit set. * @returns {Boolean} */ -InvItem.prototype.isWitness = function isWitness() { +InvItem.prototype.hasWitness = function hasWitness() { return (this.type & constants.WITNESS_MASK) !== 0; }; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 433e7885..cb6a9a4a 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -343,6 +343,18 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) { return Parser.parseUTXOs(p); case 'feefilter': return Parser.parseFeeFilter(p); + case 'encinit': + return Parser.parseEncinit(p); + case 'encack': + return Parser.parseEncack(p); + case 'sendcmpct': + return Parser.parseSendCmpct(p); + case 'cmpctblock': + return Parser.parseCmpctBlock(p); + case 'getblocktxn': + return Parser.parseGetBlockTxn(p); + case 'blocktxn': + return Parser.parseBlockTxn(p); default: return p; } @@ -654,6 +666,32 @@ Parser.parseFeeFilter = function parseFeeFilter(p) { }; }; +Parser.parseEncinit = function parseEncinit(p) { + // Handled elsewhere. + return p; +}; + +Parser.parseEncack = function parseEncack(p) { + // Handled elsewhere. + return p; +}; + +Parser.parseSendCmpct = function parseSendCmpct(p) { + return bcoin.bip152.SendCompact.fromRaw(p); +}; + +Parser.parseCmpctBlock = function parseCmpctBlock(p) { + return bcoin.bip152.CompactBlock.fromRaw(p); +}; + +Parser.parseGetBlockTxn = function parseGetBlockTxn(p) { + return bcoin.bip152.BlockTXRequest.fromRaw(p); +}; + +Parser.parseBlockTxn = function parseBlockTxn(p) { + return bcoin.bip152.BlockTX.fromRaw(p); +}; + /** * Packet * @constructor