peer: refactor version packet handling.

This commit is contained in:
Christopher Jeffrey 2017-01-16 02:50:54 -08:00
parent 212a69697e
commit db49f44c8a
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 138 additions and 247 deletions

View File

@ -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

View File

@ -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.'));
};
/**

View File

@ -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.'));
};
/**

View File

@ -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}

View File

@ -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;

View File

@ -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 '<Peer:'
+ ' ack=' + this.ack
+ ' handshake=' + this.handshake
+ ' host=' + this.hostname
+ ' outbound=' + this.outbound
+ ' ping=' + this.minPing

View File

@ -747,10 +747,10 @@ Pool.prototype.handleOpen = function handleOpen(peer) {
if (!peer.outbound)
return;
this.hosts.markAck(peer.hostname, peer.version.services);
this.hosts.markAck(peer.hostname, peer.services);
// If we don't have an ack'd loader yet, use this peer.
if (!this.peers.load || !this.peers.load.ack)
if (!this.peers.load || !this.peers.load.handshake)
this.setLoader(peer);
};
@ -1091,7 +1091,7 @@ Pool.prototype._handleBlockInv = co(function* handleBlockInv(peer, hashes) {
if (!this.chain.synced && !peer.isLoader())
return;
if (this.options.witness && !peer.haveWitness)
if (this.options.witness && !peer.hasWitness())
return;
// Request headers instead.
@ -1543,7 +1543,7 @@ Pool.prototype.getBlock = function getBlock(peer, hash) {
if (!this.loaded)
return;
if (!peer.ack)
if (!peer.handshake)
throw new Error('Peer handshake not complete (getdata).');
if (peer.destroyed)
@ -1596,7 +1596,7 @@ Pool.prototype.getTX = function getTX(peer, hashes) {
if (!this.loaded)
return;
if (!peer.ack)
if (!peer.handshake)
throw new Error('Peer handshake not complete (getdata).');
if (peer.destroyed)

View File

@ -95,42 +95,6 @@ NetAddress.fromOptions = function fromOptions(options) {
return new NetAddress().fromOptions(options);
};
/**
* Test whether the NETWORK service bit is set.
* @returns {Boolean}
*/
NetAddress.prototype.hasNetwork = function hasNetwork() {
return (this.services & common.services.NETWORK) !== 0;
};
/**
* Test whether the BLOOM service bit is set.
* @returns {Boolean}
*/
NetAddress.prototype.hasBloom = function hasBloom() {
return (this.services & common.services.BLOOM) !== 0;
};
/**
* Test whether the GETUTXO service bit is set.
* @returns {Boolean}
*/
NetAddress.prototype.hasUTXO = function hasUTXO() {
return (this.services & common.services.GETUTXO) !== 0;
};
/**
* Test whether the WITNESS service bit is set.
* @returns {Boolean}
*/
NetAddress.prototype.hasWitness = function hasWitness() {
return (this.services & common.services.WITNESS) !== 0;
};
/**
* Test whether required services are available.
* @param {Number} services