From db49f44c8a5788a75b8a2837f4cb19168ccb540c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 16 Jan 2017 02:50:54 -0800 Subject: [PATCH] peer: refactor version packet handling. --- lib/http/rpc.js | 6 +- lib/net/bip150.js | 11 +-- lib/net/bip151.js | 11 +-- lib/net/common.js | 32 ++++++++ lib/net/packets.js | 132 +++---------------------------- lib/net/peer.js | 147 +++++++++++++++++++---------------- lib/net/pool.js | 10 +-- lib/primitives/netaddress.js | 36 --------- 8 files changed, 138 insertions(+), 247 deletions(-) diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 5bfa21ff..a96da85f 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -532,10 +532,10 @@ RPC.prototype.getpeerinfo = co(function* getpeerinfo(args) { ? (peer.lastPong - peer.lastPing) / 1000 : -1, minping: peer.minPing !== -1 ? peer.minPing / 1000 : -1, - version: peer.version ? peer.version.version : 0, - subver: peer.version ? peer.version.agent : '', + version: peer.version, + subver: peer.agent, inbound: !peer.outbound, - startingheight: peer.version ? peer.version.height : -1, + startingheight: peer.height, banscore: peer.banScore, inflight: peer.requestMap.keys().map(util.revHex), whitelisted: false diff --git a/lib/net/bip150.js b/lib/net/bip150.js index 705ebba2..177accbf 100644 --- a/lib/net/bip150.js +++ b/lib/net/bip150.js @@ -333,15 +333,10 @@ BIP150.prototype.findAuthorized = function findAuthorized(hash) { */ BIP150.prototype.destroy = function destroy() { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } + if (!this.job) + return; - if (this.onAuth) { - this.removeListener('auth', this.onAuth); - this.onAuth = null; - } + this.reject(new Error('BIP150 stream was destroyed.')); }; /** diff --git a/lib/net/bip151.js b/lib/net/bip151.js index 10695c3f..8cf8f8df 100644 --- a/lib/net/bip151.js +++ b/lib/net/bip151.js @@ -547,15 +547,10 @@ BIP151.prototype._wait = function wait(timeout, resolve, reject) { */ BIP151.prototype.destroy = function destroy() { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } + if (!this.job) + return; - if (this.onShake) { - this.removeListener('handshake', this.onShake); - this.onShake = null; - } + this.reject(new Error('BIP151 stream was destroyed.')); }; /** diff --git a/lib/net/common.js b/lib/net/common.js index 9ddc63a3..466bd128 100644 --- a/lib/net/common.js +++ b/lib/net/common.js @@ -25,6 +25,38 @@ exports.PROTOCOL_VERSION = 70015; exports.MIN_VERSION = 70001; +/** + * Minimum version for getheaders. + * @const {Number} + * @default + */ + +exports.HEADERS_VERSION = 31800; + +/** + * Minimum version for bip37. + * @const {Number} + * @default + */ + +exports.BLOOM_VERSION = 70011; + +/** + * Minimum version for bip152. + * @const {Number} + * @default + */ + +exports.SENDHEADERS_VERSION = 7012; + +/** + * Minimum version for bip152. + * @const {Number} + * @default + */ + +exports.COMPACT_VERSION = 70014; + /** * Service bits. * @enum {Number} diff --git a/lib/net/packets.js b/lib/net/packets.js index 061fc793..b7900e30 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -59,18 +59,17 @@ exports.types = { MERKLEBLOCK: 21, GETUTXOS: 22, UTXOS: 23, - HAVEWITNESS: 24, - FEEFILTER: 25, - SENDCMPCT: 26, - CMPCTBLOCK: 27, - GETBLOCKTXN: 28, - BLOCKTXN: 29, - ENCINIT: 30, - ENCACK: 31, - AUTHCHALLENGE: 32, - AUTHREPLY: 33, - AUTHPROPOSE: 34, - UNKNOWN: 35, + FEEFILTER: 24, + SENDCMPCT: 25, + CMPCTBLOCK: 26, + GETBLOCKTXN: 27, + BLOCKTXN: 28, + ENCINIT: 29, + ENCACK: 30, + AUTHCHALLENGE: 31, + AUTHREPLY: 32, + AUTHPROPOSE: 33, + UNKNOWN: 34, // Internal INTERNAL: 100, DATA: 101 @@ -283,71 +282,6 @@ VersionPacket.prototype.toRaw = function toRaw() { return this.toWriter(new StaticWriter(size)).render(); }; -/** - * Test whether the NETWORK service bit is set. - * @returns {Boolean} - */ - -VersionPacket.prototype.hasNetwork = function hasNetwork() { - return (this.services & common.services.NETWORK) !== 0; -}; - -/** - * Test whether the BLOOM service bit is set. - * @returns {Boolean} - */ - -VersionPacket.prototype.hasBloom = function hasBloom() { - return this.version >= 70011 - && (this.services & common.services.BLOOM) !== 0; -}; - -/** - * Test whether the GETUTXO service bit is set. - * @returns {Boolean} - */ - -VersionPacket.prototype.hasUTXO = function hasUTXO() { - return (this.services & common.services.GETUTXO) !== 0; -}; - -/** - * Test whether the WITNESS service bit is set. - * @returns {Boolean} - */ - -VersionPacket.prototype.hasWitness = function hasWitness() { - return (this.services & common.services.WITNESS) !== 0; -}; - -/** - * Test whether required services are available. - * @param {Number} services - * @returns {Boolean} - */ - -VersionPacket.prototype.hasServices = function hasServices(services) { - return (this.services & services) === services; -}; - -/** - * Test whether the protocol version supports getheaders. - * @returns {Boolean} - */ - -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 buffer reader. * @private @@ -2821,47 +2755,6 @@ UTXOsPacket.fromRaw = function fromRaw(data, enc) { return new UTXOsPacket().fromRaw(data); }; -/** - * Represents a `havewitness` packet. - * @exports HaveWitnessPacket - * @constructor - */ - -function HaveWitnessPacket() { - if (!(this instanceof HaveWitnessPacket)) - return new HaveWitnessPacket(); - - Packet.call(this); -} - -util.inherits(HaveWitnessPacket, Packet); - -HaveWitnessPacket.prototype.cmd = 'havewitness'; -HaveWitnessPacket.prototype.type = exports.types.HAVEWITNESS; - -/** - * Instantiate havewitness packet from buffer reader. - * @param {BufferReader} br - * @returns {HaveWitnessPacket} - */ - -HaveWitnessPacket.fromReader = function fromReader(br) { - return new HaveWitnessPacket().fromReader(br); -}; - -/** - * Instantiate havewitness packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {HaveWitnessPacket} - */ - -HaveWitnessPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = new Buffer(data, enc); - return new HaveWitnessPacket().fromRaw(data); -}; - /** * Represents a `feefilter` packet. * @exports FeeFilterPacket @@ -3973,8 +3866,6 @@ exports.fromRaw = function fromRaw(cmd, data) { return GetUTXOsPacket.fromRaw(data); case 'utxos': return UTXOsPacket.fromRaw(data); - case 'havewitness': - return HaveWitnessPacket.fromRaw(data); case 'feefilter': return FeeFilterPacket.fromRaw(data); case 'sendcmpct': @@ -4029,7 +3920,6 @@ exports.FilterClearPacket = FilterClearPacket; exports.MerkleBlockPacket = MerkleBlockPacket; exports.GetUTXOsPacket = GetUTXOsPacket; exports.UTXOsPacket = UTXOsPacket; -exports.HaveWitnessPacket = HaveWitnessPacket; exports.FeeFilterPacket = FeeFilterPacket; exports.SendCmpctPacket = SendCmpctPacket; exports.CmpctBlockPacket = CmpctBlockPacket; diff --git a/lib/net/peer.js b/lib/net/peer.js index 0c596a90..b3456b10 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -28,6 +28,7 @@ var TX = require('../primitives/tx'); var encoding = require('../utils/encoding'); var errors = require('../protocol/errors'); var NetAddress = require('../primitives/netaddress'); +var services = common.services; var invTypes = InvItem.types; var packetTypes = packets.types; var VerifyResult = errors.VerifyResult; @@ -48,15 +49,13 @@ var VerifyResult = errors.VerifyResult; * @property {Framer} framer * @property {Chain} chain * @property {Mempool} mempool - * @property {Object?} version - Version packet payload. + * @property {Number} version * @property {Boolean} destroyed * @property {Boolean} ack - Whether verack has been received. * @property {Boolean} connected * @property {Number} ts * @property {Boolean} preferHeaders - Whether the peer has * requested getheaders. - * @property {Boolean} haveWitness - Whether the peer supports segwit, - * either notified via service bits or deprecated `havewitness` packet. * @property {Hash?} hashContinue - The block hash at which to continue * the sync for the peer. * @property {Bloom?} spvFilter - The _peer's_ bloom spvFilter. @@ -94,6 +93,7 @@ function Peer(pool) { this.connected = false; this.destroyed = false; this.ack = false; + this.handshake = false; this.ts = 0; this.lastSend = 0; this.lastRecv = 0; @@ -103,12 +103,14 @@ function Peer(pool) { this.banScore = 0; this.invQueue = []; - this.version = null; + this.version = -1; + this.services = 0; + this.height = -1; + this.agent = null; + this.noRelay = false; this.preferHeaders = false; - this.haveWitness = false; this.hashContinue = null; this.spvFilter = null; - this.noRelay = false; this.feeRate = -1; this.bip151 = null; this.bip150 = null; @@ -490,6 +492,8 @@ Peer.prototype.initStall = function initStall() { */ Peer.prototype.initBIP151 = co(function* initBIP151() { + assert(!this.destroyed); + // Send encinit. Wait for handshake to complete. if (!this.bip151) return; @@ -523,9 +527,12 @@ Peer.prototype.initBIP151 = co(function* initBIP151() { */ Peer.prototype.initBIP150 = co(function* initBIP150() { - if (!this.bip151 || !this.bip150) + assert(!this.destroyed); + + if (!this.bip150) return; + assert(this.bip151); assert(!this.bip150.completed); if (!this.bip151.handshake) @@ -562,34 +569,29 @@ Peer.prototype.initVersion = co(function* initVersion() { // Say hello. this.sendVersion(); - // Advertise our address. - if (!this.pool.address.isNull() - && !this.options.selfish - && this.pool.server) { - this.send(new packets.AddrPacket([this.pool.address])); - } - if (!this.ack) { yield this.wait(packetTypes.VERACK, 10000); assert(this.ack); } // Wait for _their_ version. - if (!this.version) { + if (this.version === -1) { this.logger.debug( 'Peer sent a verack without a version (%s).', this.hostname); yield this.wait(packetTypes.VERSION, 10000); - assert(this.version); + assert(this.version !== -1); } - this.logger.debug('Received verack (%s).', this.hostname); + this.handshake = true; + + this.logger.debug('Version handshake complete (%s).', this.hostname); }); /** - * Handle `ack` event (called on verack). + * Finalize peer after handshake. * @private */ @@ -608,15 +610,22 @@ Peer.prototype.finalize = co(function* finalize() { self.flushInv(); }, Peer.INV_INTERVAL); + // Advertise our address. + if (!this.pool.address.isNull() + && !this.options.selfish + && this.options.listen) { + this.send(new packets.AddrPacket([this.pool.address])); + } + // Ask for headers-only. if (this.options.headers) { - if (this.version.version >= 70012) + if (this.version >= common.SENDHEADERS_VERSION) this.send(new packets.SendHeadersPacket()); } // We want compact blocks! if (this.options.compact) { - if (this.version.version >= 70014) + if (this.version >= common.COMPACT_VERSION) this.sendCompact(); } @@ -656,7 +665,7 @@ Peer.prototype.announceBlock = function announceBlock(blocks) { var inv = []; var i, block; - if (!this.ack) + if (!this.handshake) return; if (this.destroyed) @@ -709,7 +718,7 @@ Peer.prototype.announceTX = function announceTX(txs) { var inv = []; var i, tx, hash, entry; - if (!this.ack) + if (!this.handshake) return; if (this.destroyed) @@ -796,7 +805,7 @@ Peer.prototype.sendInv = function sendInv(items) { var hasBlock = false; var i, item; - if (!this.ack) + if (!this.handshake) return; if (this.destroyed) @@ -857,7 +866,7 @@ Peer.prototype.flushInv = function flushInv() { Peer.prototype.sendHeaders = function sendHeaders(items) { var i, item, chunk; - if (!this.ack) + if (!this.handshake) return; if (this.destroyed) @@ -918,10 +927,10 @@ Peer.prototype.sendGetAddr = function sendGetAddr() { */ Peer.prototype.sendPing = function sendPing() { - if (!this.version) + if (!this.handshake) return; - if (this.version.version <= 60000) { + if (this.version <= 60000) { this.send(new packets.PingPacket()); return; } @@ -942,7 +951,7 @@ Peer.prototype.sendPing = function sendPing() { */ Peer.prototype.updateWatch = function updateWatch() { - if (!this.ack) + if (!this.handshake) return; if (!this.options.spv) @@ -957,7 +966,7 @@ Peer.prototype.updateWatch = function updateWatch() { */ Peer.prototype.sendFeeRate = function sendFeeRate(rate) { - if (!this.ack) + if (!this.handshake) return; this.send(new packets.FeeFilterPacket(rate)); @@ -1321,7 +1330,7 @@ Peer.prototype.blockType = function blockType() { } } - if (this.haveWitness) + if (this.hasWitness()) return invTypes.WITNESS_BLOCK; return invTypes.BLOCK; @@ -1333,7 +1342,7 @@ Peer.prototype.blockType = function blockType() { */ Peer.prototype.txType = function txType() { - if (this.haveWitness) + if (this.hasWitness()) return invTypes.WITNESS_TX; return invTypes.TX; @@ -1434,7 +1443,7 @@ Peer.prototype.onPacket = co(function* onPacket(packet) { && !this.bip151.completed && packet.type !== packetTypes.ENCINIT && packet.type !== packetTypes.ENCACK) { - this.bip151.complete(new Error('Message before handshake.')); + this.bip151.reject(new Error('Message before handshake.')); } if (this.bip150 @@ -1442,7 +1451,7 @@ Peer.prototype.onPacket = co(function* onPacket(packet) { && packet.type !== packetTypes.AUTHCHALLENGE && packet.type !== packetTypes.AUTHREPLY && packet.type !== packetTypes.AUTHPROPOSE) { - this.bip150.complete(new Error('Message before auth.')); + this.bip150.reject(new Error('Message before auth.')); } if (this.lastMerkle) { @@ -1525,9 +1534,6 @@ Peer.prototype.onPacket = co(function* onPacket(packet) { case packetTypes.UTXOS: yield this.handleUTXOs(packet); break; - case packetTypes.HAVEWITNESS: - yield this.handleHaveWitness(packet); - break; case packetTypes.FEEFILTER: yield this.handleFeeFilter(packet); break; @@ -1750,17 +1756,6 @@ Peer.prototype.handleGetUTXOs = co(function* handleGetUTXOs(packet) { this.send(utxos); }); -/** - * Handle `havewitness` packet. - * @private - * @param {HaveWitnessPacket} - */ - -Peer.prototype.handleHaveWitness = co(function* handleHaveWitness(packet) { - this.haveWitness = true; - this.emit('havewitness'); -}); - /** * Handle `getheaders` packet. * @private @@ -1860,39 +1855,42 @@ Peer.prototype.handleGetBlocks = co(function* handleGetBlocks(packet) { */ Peer.prototype.handleVersion = co(function* handleVersion(packet) { - if (this.version) + if (this.version !== -1) throw new Error('Peer sent a duplicate version.'); - this.version = packet; + this.version = packet.version; + this.services = packet.services; + this.height = packet.height; + this.agent = packet.agent; this.noRelay = packet.noRelay; - if (this.options.witness) - this.haveWitness = packet.hasWitness(); - if (!this.network.selfConnect) { if (util.equal(packet.nonce, this.pool.nonce)) throw new Error('We connected to ourself. Oops.'); } - if (packet.version < common.MIN_VERSION) + if (this.version < common.MIN_VERSION) throw new Error('Peer does not support required protocol version.'); if (this.outbound) { - if (!packet.hasNetwork()) + if (!(this.services & services.NETWORK)) throw new Error('Peer does not support network services.'); if (this.options.headers) { - if (!packet.hasHeaders()) + if (this.version < common.HEADERS_VERSION) throw new Error('Peer does not support getheaders.'); } if (this.options.spv) { - if (!packet.hasBloom()) + if (!(this.services & services.BLOOM)) + throw new Error('Peer does not support BIP37.'); + + if (this.version < common.BLOOM_VERSION) throw new Error('Peer does not support BIP37.'); } if (this.options.witness) { - if (!this.haveWitness) + if (!(this.services & services.WITNESS)) throw new Error('Peer does not support segregated witness.'); } } @@ -1911,6 +1909,7 @@ Peer.prototype.handleVersion = co(function* handleVersion(packet) { Peer.prototype.handleVerack = co(function* handleVerack(packet) { this.ack = true; this.emit('verack'); + this.logger.debug('Received verack (%s).', this.hostname); }); /** @@ -2797,7 +2796,7 @@ Peer.prototype.handleBlockTxn = co(function* handleBlockTxn(packet) { */ Peer.prototype.sendAlert = function sendAlert(alert) { - if (!this.ack) + if (!this.handshake) return; if (!this.invFilter.added(alert.hash())) @@ -2872,13 +2871,10 @@ Peer.prototype.sendGetBlocks = function getBlocks(locator, stop) { */ Peer.prototype.sendMempool = function sendMempool() { - if (!this.ack) + if (!this.handshake) return; - if (!this.version) - return; - - if (!this.version.hasBloom()) { + if (!(this.services & services.BLOOM)) { this.logger.debug( 'Cannot request mempool for non-bloom peer (%s).', this.hostname); @@ -3026,16 +3022,16 @@ Peer.prototype.sync = co(function* sync() { if (!this.pool.syncing) return; - if (!this.ack) + if (!this.handshake) return; if (this.syncSent) return; - if (!this.version.hasNetwork()) + if (!(this.services & services.NETWORK)) return; - if (this.options.witness && !this.haveWitness) + if (this.options.witness && !this.hasWitness()) return; if (!this.isLoader()) { @@ -3063,6 +3059,25 @@ Peer.prototype.sync = co(function* sync() { return yield this.getBlocks(); }); +/** + * Test whether required services are available. + * @param {Number} services + * @returns {Boolean} + */ + +Peer.prototype.hasServices = function hasServices(services) { + return (this.services & services) === services; +}; + +/** + * Test whether the WITNESS service bit is set. + * @returns {Boolean} + */ + +Peer.prototype.hasWitness = function hasWitness() { + return (this.services & services.WITNESS) !== 0; +}; + /** * Inspect the peer. * @returns {String} @@ -3070,7 +3085,7 @@ Peer.prototype.sync = co(function* sync() { Peer.prototype.inspect = function inspect() { return '