1213 lines
27 KiB
JavaScript
1213 lines
27 KiB
JavaScript
/**
|
|
* peer.js - peer object for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
*/
|
|
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
var bcoin = require('../bcoin');
|
|
var bn = require('bn.js');
|
|
var utils = require('./utils');
|
|
var assert = utils.assert;
|
|
var constants = bcoin.protocol.constants;
|
|
var network = bcoin.protocol.network;
|
|
|
|
/**
|
|
* Peer
|
|
*/
|
|
|
|
function Peer(pool, options) {
|
|
if (!(this instanceof Peer))
|
|
return new Peer(pool, options);
|
|
|
|
EventEmitter.call(this);
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
this.options = options;
|
|
this.pool = pool;
|
|
this.socket = null;
|
|
this.host = null;
|
|
this.port = 0;
|
|
this._createSocket = this.options.createSocket;
|
|
this.priority = this.options.priority;
|
|
this.parser = new bcoin.protocol.parser();
|
|
this.framer = new bcoin.protocol.framer();
|
|
this.chain = this.pool.chain;
|
|
this.mempool = this.pool.mempool;
|
|
this.bloom = this.pool.bloom;
|
|
this.version = null;
|
|
this.destroyed = false;
|
|
this.ack = false;
|
|
this.connected = false;
|
|
this.ts = this.options.ts || 0;
|
|
this.sendHeaders = false;
|
|
this.haveWitness = false;
|
|
this.hashContinue = null;
|
|
this.filter = null;
|
|
this.relay = true;
|
|
|
|
this.challenge = null;
|
|
this.lastPong = 0;
|
|
|
|
this.banScore = 0;
|
|
|
|
if (options.socket) {
|
|
this.socket = options.socket;
|
|
this.host = this.socket.remoteAddress;
|
|
this.port = this.socket.remotePort;
|
|
assert(this.host);
|
|
assert(this.port != null);
|
|
} else if (options.seed) {
|
|
options.seed = utils.parseHost(options.seed);
|
|
options.seed.port = options.seed.port || network.port;
|
|
this.socket = this.createSocket(options.seed.port, options.seed.host);
|
|
}
|
|
|
|
if (!this.socket)
|
|
throw new Error('No socket');
|
|
|
|
this._broadcast = {
|
|
timeout: this.options.broadcastTimeout || 30000,
|
|
interval: this.options.broadcastInterval || 3000,
|
|
map: {}
|
|
};
|
|
|
|
this._request = {
|
|
timeout: this.options.requestTimeout || 10000,
|
|
cont: {},
|
|
skip: {},
|
|
queue: []
|
|
};
|
|
|
|
this._ping = {
|
|
timer: null,
|
|
interval: this.options.pingInterval || 30000
|
|
};
|
|
|
|
this.queue = {
|
|
block: [],
|
|
tx: []
|
|
};
|
|
|
|
Peer.uid.iaddn(1);
|
|
|
|
this.id = Peer.uid.toString(10);
|
|
|
|
this.setMaxListeners(10000);
|
|
|
|
this._init();
|
|
}
|
|
|
|
utils.inherits(Peer, EventEmitter);
|
|
|
|
Peer.uid = new bn(0);
|
|
|
|
Peer.prototype._init = function init() {
|
|
var self = this;
|
|
|
|
if (!this.host)
|
|
this.host = this.socket.remoteAddress || this.socket._host || null;
|
|
|
|
if (!this.port)
|
|
this.port = this.socket.remotePort || 0;
|
|
|
|
this.socket.once('connect', function() {
|
|
self.ts = utils.now();
|
|
self.connected = true;
|
|
if (!self.host)
|
|
self.host = self.socket.remoteAddress;
|
|
if (!self.port)
|
|
self.port = self.socket.remotePort;
|
|
self.emit('connect');
|
|
});
|
|
|
|
this.socket.once('error', function(err) {
|
|
self._error(err);
|
|
self.setMisbehavior(100);
|
|
});
|
|
|
|
this.socket.once('close', function() {
|
|
self._error('socket hangup');
|
|
self.connected = false;
|
|
});
|
|
|
|
this.socket.on('data', function(chunk) {
|
|
self.parser.feed(chunk);
|
|
});
|
|
|
|
this.parser.on('packet', function(packet) {
|
|
self._onPacket(packet);
|
|
});
|
|
|
|
this.parser.on('error', function(err) {
|
|
utils.debug(err.stack + '');
|
|
self.sendReject(null, 'malformed', 'error parsing message', 100);
|
|
self._error(err);
|
|
// Something is wrong here.
|
|
// Ignore this peer.
|
|
self.setMisbehavior(100);
|
|
});
|
|
|
|
this.challenge = utils.nonce();
|
|
|
|
this._ping.timer = setInterval(function() {
|
|
self._write(self.framer.ping({
|
|
nonce: self.challenge
|
|
}));
|
|
}, this._ping.interval);
|
|
|
|
this._req('verack', function(err, payload) {
|
|
if (err) {
|
|
self._error(err);
|
|
self.destroy();
|
|
return;
|
|
}
|
|
|
|
self.ack = true;
|
|
self.emit('ack');
|
|
self.ts = utils.now();
|
|
|
|
self._write(self.framer.getAddr());
|
|
|
|
if (self.options.headers) {
|
|
if (self.version && self.version.version > 70012)
|
|
self._write(self.framer.sendHeaders());
|
|
}
|
|
|
|
if (self.options.witness) {
|
|
if (self.version && self.version.version >= 70012)
|
|
self._write(self.framer.haveWitness());
|
|
}
|
|
|
|
if (self.chain.isFull())
|
|
self.getMempool();
|
|
});
|
|
|
|
// Send hello
|
|
this._write(this.framer.version({
|
|
height: this.chain.height,
|
|
relay: this.options.relay
|
|
}));
|
|
};
|
|
|
|
Peer.prototype.createSocket = function createSocket(port, host) {
|
|
var self = this;
|
|
var socket, net;
|
|
|
|
assert(port != null);
|
|
assert(host);
|
|
|
|
this.host = host;
|
|
this.port = port;
|
|
|
|
if (this._createSocket) {
|
|
socket = this._createSocket(port, host);
|
|
} else if (bcoin.isBrowser) {
|
|
throw new Error('Please include a `createSocket` callback.');
|
|
} else {
|
|
net = require('n' + 'et');
|
|
socket = net.connect(port, host);
|
|
}
|
|
|
|
utils.debug(
|
|
'Connecting to %s:%d (priority=%s)',
|
|
host, port, this.priority);
|
|
|
|
socket.on('connect', function() {
|
|
utils.debug(
|
|
'Connected to %s:%d (priority=%s)',
|
|
host, port, self.priority);
|
|
});
|
|
|
|
return socket;
|
|
};
|
|
|
|
Peer.prototype.broadcast = function broadcast(items) {
|
|
var self = this;
|
|
var result = [];
|
|
var payload = [];
|
|
|
|
if (!this.relay)
|
|
return;
|
|
|
|
if (this.destroyed)
|
|
return;
|
|
|
|
if (!Array.isArray(items))
|
|
items = [items];
|
|
|
|
items.forEach(function(item) {
|
|
var key = item.hash('hex');
|
|
var old = this._broadcast.map[key];
|
|
var type = item.type;
|
|
var entry, packetType;
|
|
|
|
if (typeof type === 'string')
|
|
type = constants.inv[type];
|
|
|
|
// INV does not set the witness
|
|
// mask (only GETDATA does this).
|
|
type &= ~constants.invWitnessMask;
|
|
|
|
if (type === constants.inv.block)
|
|
packetType = 'block';
|
|
else if (type === constants.inv.tx)
|
|
packetType = 'tx';
|
|
else if (type === constants.inv.filteredblock)
|
|
packetType = 'merkleblock';
|
|
else
|
|
assert(false, 'Bad type.');
|
|
|
|
if (self.filter && type === constants.inv.tx) {
|
|
if (!item.isWatched(self.filter))
|
|
return;
|
|
}
|
|
|
|
if (old) {
|
|
clearTimeout(old.timer);
|
|
clearInterval(old.interval);
|
|
}
|
|
|
|
// Auto-cleanup broadcast map after timeout
|
|
entry = {
|
|
e: new EventEmitter(),
|
|
timeout: setTimeout(function() {
|
|
entry.e.emit('timeout');
|
|
clearInterval(entry.interval);
|
|
delete self._broadcast.map[key];
|
|
}, this._broadcast.timeout),
|
|
|
|
// Retransmit
|
|
interval: setInterval(function() {
|
|
self._write(entry.inv);
|
|
}, this._broadcast.interval),
|
|
|
|
inv: this.framer.inv([{
|
|
type: type,
|
|
hash: item.hash()
|
|
}]),
|
|
|
|
packetType: packetType,
|
|
type: type,
|
|
hash: item.hash(),
|
|
value: item.renderNormal(),
|
|
witnessValue: item.renderWitness()
|
|
};
|
|
|
|
this._broadcast.map[key] = entry;
|
|
|
|
result.push(entry.e);
|
|
|
|
payload.push({
|
|
type: entry.type,
|
|
hash: entry.hash
|
|
});
|
|
}, this);
|
|
|
|
this._write(this.framer.inv(payload));
|
|
|
|
return result;
|
|
};
|
|
|
|
Peer.prototype.updateWatch = function updateWatch() {
|
|
if (!this.pool.options.spv)
|
|
return;
|
|
|
|
if (this.ack)
|
|
this._write(this.framer.filterLoad(this.bloom, 'none'));
|
|
};
|
|
|
|
Peer.prototype.destroy = function destroy() {
|
|
var i;
|
|
|
|
if (this.destroyed)
|
|
return;
|
|
|
|
this.destroyed = true;
|
|
this.socket.destroy();
|
|
this.socket = null;
|
|
this.emit('close');
|
|
|
|
// Clean-up timeouts
|
|
Object.keys(this._broadcast.map).forEach(function(key) {
|
|
clearTimeout(this._broadcast.map[key].timer);
|
|
clearInterval(this._broadcast.map[key].interval);
|
|
}, this);
|
|
|
|
clearInterval(this._ping.timer);
|
|
this._ping.timer = null;
|
|
|
|
for (i = 0; i < this._request.queue.length; i++)
|
|
clearTimeout(this._request.queue[i].timer);
|
|
};
|
|
|
|
Peer.prototype._write = function write(chunk) {
|
|
if (this.destroyed)
|
|
return;
|
|
|
|
this.socket.write(chunk);
|
|
};
|
|
|
|
Peer.prototype._error = function error(err) {
|
|
if (this.destroyed)
|
|
return;
|
|
|
|
if (typeof err === 'string')
|
|
err = new Error(err);
|
|
|
|
err.message += ' (' + this.host + ')';
|
|
|
|
this.destroy();
|
|
this.emit('error', err);
|
|
};
|
|
|
|
Peer.prototype._req = function _req(cmd, cb) {
|
|
var self = this;
|
|
var entry;
|
|
|
|
if (this.destroyed)
|
|
return utils.asyncify(cb)(new Error('Destroyed, sorry'));
|
|
|
|
entry = {
|
|
cmd: cmd,
|
|
cb: cb,
|
|
ontimeout: function() {
|
|
var i = self._request.queue.indexOf(entry);
|
|
if (i !== -1) {
|
|
self._request.queue.splice(i, 1);
|
|
cb(new Error('Timed out: ' + cmd), null);
|
|
}
|
|
},
|
|
timer: null
|
|
};
|
|
|
|
entry.timer = setTimeout(entry.ontimeout, this._request.timeout);
|
|
|
|
this._request.queue.push(entry);
|
|
|
|
return entry;
|
|
};
|
|
|
|
Peer.prototype._res = function _res(cmd, payload) {
|
|
var i, entry, res;
|
|
|
|
for (i = 0; i < this._request.queue.length; i++) {
|
|
entry = this._request.queue[i];
|
|
|
|
if (!entry || (entry.cmd && entry.cmd !== cmd))
|
|
return false;
|
|
|
|
res = entry.cb(null, payload, cmd);
|
|
|
|
if (res === this._request.cont) {
|
|
assert(!entry.cmd);
|
|
|
|
// Restart timer
|
|
if (!this.destroyed)
|
|
entry.timer = setTimeout(entry.ontimeout, this._request.timeout);
|
|
return true;
|
|
} else if (res !== this._request.skip) {
|
|
this._request.queue.shift();
|
|
clearTimeout(entry.timer);
|
|
entry.timer = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
Peer.prototype.getData = function getData(items) {
|
|
this._write(this.framer.getData(items));
|
|
};
|
|
|
|
Peer.prototype._onPacket = function onPacket(packet) {
|
|
var cmd = packet.cmd;
|
|
var payload = packet.payload;
|
|
|
|
if (this.lastBlock && cmd !== 'tx')
|
|
this._emitMerkle();
|
|
|
|
switch (cmd) {
|
|
case 'version':
|
|
return this._handleVersion(payload);
|
|
case 'inv':
|
|
return this._handleInv(payload);
|
|
case 'headers':
|
|
return this._handleHeaders(payload);
|
|
case 'getdata':
|
|
return this._handleGetData(payload);
|
|
case 'addr':
|
|
return this._handleAddr(payload);
|
|
case 'ping':
|
|
return this._handlePing(payload);
|
|
case 'pong':
|
|
return this._handlePong(payload);
|
|
case 'getaddr':
|
|
return this._handleGetAddr(payload);
|
|
case 'reject':
|
|
return this._handleReject(payload);
|
|
case 'alert':
|
|
return this._handleAlert(payload);
|
|
case 'getutxos':
|
|
return this._handleGetUTXOs(payload);
|
|
case 'utxos':
|
|
return this._handleUTXOs(payload);
|
|
case 'getblocks':
|
|
return this._handleGetBlocks(payload);
|
|
case 'getheaders':
|
|
return this._handleGetHeaders(payload);
|
|
case 'mempool':
|
|
return this._handleMempool(payload);
|
|
case 'filterload':
|
|
return this._handleFilterLoad(payload);
|
|
case 'filteradd':
|
|
return this._handleFilterAdd(payload);
|
|
case 'filterclear':
|
|
return this._handleFilterClear(payload);
|
|
case 'block':
|
|
payload = bcoin.compactblock(payload);
|
|
this._emit(cmd, payload);
|
|
break;
|
|
case 'merkleblock':
|
|
payload = bcoin.merkleblock(payload);
|
|
this.lastBlock = payload;
|
|
break;
|
|
case 'tx':
|
|
payload = bcoin.tx(payload, this.lastBlock);
|
|
if (this.lastBlock) {
|
|
if (payload.block) {
|
|
this.lastBlock.txs.push(payload);
|
|
break;
|
|
}
|
|
this._emitMerkle();
|
|
}
|
|
this._emit(cmd, payload);
|
|
break;
|
|
case 'sendheaders':
|
|
this.sendHeaders = true;
|
|
this._res(cmd, payload);
|
|
break;
|
|
case 'havewitness':
|
|
this.haveWitness = true;
|
|
this._res(cmd, payload);
|
|
break;
|
|
case 'verack':
|
|
this._emit(cmd, payload);
|
|
break;
|
|
default:
|
|
utils.debug('Unknown packet: %s', cmd);
|
|
this._emit(cmd, payload);
|
|
break;
|
|
}
|
|
};
|
|
|
|
Peer.prototype._emit = function _emit(cmd, payload) {
|
|
if (this._res(cmd, payload))
|
|
return;
|
|
|
|
this.emit(cmd, payload);
|
|
};
|
|
|
|
Peer.prototype._emitMerkle = function _emitMerkle() {
|
|
if (this.lastBlock)
|
|
this._emit('merkleblock', this.lastBlock);
|
|
this.lastBlock = null;
|
|
};
|
|
|
|
Peer.prototype._handleFilterLoad = function _handleFilterLoad(payload) {
|
|
var size = payload.filter.length * 8;
|
|
this.filter = new bcoin.bloom(size, payload.n, payload.tweak);
|
|
this.filter.filter = payload.filter;
|
|
this.filter.update = payload.update;
|
|
this.relay = true;
|
|
};
|
|
|
|
Peer.prototype._handleFilterAdd = function _handleFilterAdd(payload) {
|
|
if (this.filter)
|
|
this.filter.add(payload.data);
|
|
this.relay = true;
|
|
};
|
|
|
|
Peer.prototype._handleFilterClear = function _handleFilterClear(payload) {
|
|
if (this.filter)
|
|
this.filter.reset();
|
|
this.relay = true;
|
|
};
|
|
|
|
Peer.prototype._handleUTXOs = function _handleUTXOs(payload) {
|
|
payload.coins = payload.coins(function(coin) {
|
|
return new bcoin.coin(coin);
|
|
});
|
|
utils.debug('Received %d utxos from %s.', payload.coins.length, this.host);
|
|
this.emit('utxos', payload);
|
|
};
|
|
|
|
Peer.prototype._handleGetUTXOs = function _handleGetUTXOs(payload) {
|
|
var self = this;
|
|
var coins = [];
|
|
var notfound = [];
|
|
|
|
if (this.pool.options.selfish)
|
|
return;
|
|
|
|
if (this.chain.db.options.spv)
|
|
return;
|
|
|
|
function checkMempool(hash, index, callback) {
|
|
if (!self.mempool)
|
|
return callback();
|
|
|
|
if (!payload.mempool)
|
|
return callback();
|
|
|
|
self.mempool.getCoin(hash, index, callback);
|
|
}
|
|
|
|
function isSpent(hash, index, callback) {
|
|
if (!self.mempool)
|
|
return callback(null, false);
|
|
|
|
if (!payload.mempool)
|
|
return callback(null, false);
|
|
|
|
self.mempool.isSpent(hash, index, callback);
|
|
}
|
|
|
|
utils.forEachSerial(payload.prevout, function(prevout, next, i) {
|
|
var hash = prevout.hash;
|
|
var index = prevout.index;
|
|
|
|
checkMempool(hash, index, function(err, coin) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (coin) {
|
|
coins.push(coin);
|
|
return next();
|
|
}
|
|
|
|
isSpent(hash, index, function(err, result) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (result) {
|
|
notfound.push(i);
|
|
return next();
|
|
}
|
|
|
|
self.chain.db.getCoin(hash, index, function(err, coin) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (!coin) {
|
|
notfound.push(i);
|
|
return next();
|
|
}
|
|
|
|
coins.push(coin);
|
|
|
|
next();
|
|
});
|
|
});
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
self.emit('error', err);
|
|
|
|
self._write(self.framer.UTXOs({
|
|
height: self.chain.height,
|
|
tip: self.chain.tip.hash,
|
|
notfound: notfound,
|
|
coins: coins
|
|
}));
|
|
});
|
|
};
|
|
|
|
Peer.prototype._handleGetHeaders = function _handleGetHeaders(payload) {
|
|
var self = this;
|
|
var headers = [];
|
|
|
|
if (this.pool.options.selfish)
|
|
return;
|
|
|
|
if (this.chain.db.options.spv)
|
|
return;
|
|
|
|
if (this.chain.db.prune)
|
|
return;
|
|
|
|
function collect(err, hash) {
|
|
if (err)
|
|
return done(err);
|
|
|
|
if (!hash)
|
|
return done();
|
|
|
|
self.chain.db.get(hash, function(err, entry) {
|
|
if (err)
|
|
return done(err);
|
|
|
|
if (!entry)
|
|
return done();
|
|
|
|
(function next(err, entry) {
|
|
if (err)
|
|
return done(err);
|
|
|
|
if (!entry)
|
|
return done();
|
|
|
|
headers.push(new bcoin.headers(entry));
|
|
|
|
if (headers.length === 2000)
|
|
return done();
|
|
|
|
if (entry.hash === payload.stop)
|
|
return done();
|
|
|
|
entry.getNext(next);
|
|
})(null, entry);
|
|
});
|
|
}
|
|
|
|
function done(err) {
|
|
if (err)
|
|
return self.emit('error', err);
|
|
|
|
self._write(self.framer.headers(headers));
|
|
}
|
|
|
|
if (!payload.locator)
|
|
return collect(null, payload.stop);
|
|
|
|
this.chain.findLocator(payload.locator, function(err, hash) {
|
|
if (err)
|
|
return collect(err);
|
|
|
|
if (!hash)
|
|
return collect();
|
|
|
|
self.chain.db.getNextHash(hash, collect);
|
|
});
|
|
};
|
|
|
|
Peer.prototype._handleGetBlocks = function _handleGetBlocks(payload) {
|
|
var self = this;
|
|
var blocks = [];
|
|
|
|
if (this.pool.options.selfish)
|
|
return;
|
|
|
|
if (this.chain.db.options.spv)
|
|
return;
|
|
|
|
if (this.chain.db.prune)
|
|
return;
|
|
|
|
function done(err) {
|
|
if (err)
|
|
return self.emit('error', err);
|
|
self._write(self.framer.inv(blocks));
|
|
}
|
|
|
|
this.chain.findLocator(payload.locator, function(err, tip) {
|
|
if (err)
|
|
return done(err);
|
|
|
|
if (!tip)
|
|
return done();
|
|
|
|
(function next(hash) {
|
|
self.chain.db.getNextHash(hash, function(err, hash) {
|
|
if (err)
|
|
return done(err);
|
|
|
|
if (!hash)
|
|
return done();
|
|
|
|
blocks.push({ type: constants.inv.block, hash: hash });
|
|
|
|
if (hash === payload.stop)
|
|
return done();
|
|
|
|
if (blocks.length === 500) {
|
|
self.hashContinue = hash;
|
|
return done();
|
|
}
|
|
|
|
next(hash);
|
|
});
|
|
})(tip);
|
|
});
|
|
};
|
|
|
|
Peer.prototype._handleVersion = function handleVersion(payload) {
|
|
if (payload.version < constants.minVersion) {
|
|
this._error('Peer doesn\'t support required protocol version.');
|
|
this.setMisbehavior(100);
|
|
return;
|
|
}
|
|
|
|
if (this.options.headers) {
|
|
if (payload.version < 31800) {
|
|
this._error('Peer doesn\'t support getheaders.');
|
|
this.setMisbehavior(100);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.options.network) {
|
|
if (!payload.network) {
|
|
this._error('Peer does not support network services.');
|
|
this.setMisbehavior(100);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.options.spv) {
|
|
if (!payload.bloom && payload.version < 70011) {
|
|
this._error('Peer does not support bip37.');
|
|
this.setMisbehavior(100);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.options.witness) {
|
|
if (!payload.witness) {
|
|
this._req('havewitness', function(err, payload) {
|
|
if (err) {
|
|
self._error('Peer does not support segregated witness.');
|
|
self.setMisbehavior(100);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if (payload.witness)
|
|
this.haveWitness = true;
|
|
|
|
if (payload.relay === false)
|
|
this.relay = false;
|
|
|
|
// ACK
|
|
this._write(this.framer.verack());
|
|
this.version = payload;
|
|
this.emit('version', payload);
|
|
};
|
|
|
|
Peer.prototype._handleMempool = function _handleMempool() {
|
|
var self = this;
|
|
var items = [];
|
|
var i;
|
|
|
|
if (!this.mempool)
|
|
return;
|
|
|
|
if (this.pool.options.selfish)
|
|
return;
|
|
|
|
this.mempool.getSnapshot(function(err, hashes) {
|
|
if (err)
|
|
return self.emit('error', err);
|
|
|
|
for (i = 0; i < hashes.length; i++)
|
|
items.push({ type: constants.inv.tx, hash: hashes[i] });
|
|
|
|
utils.debug('Sending mempool snapshot to %s.', self.host);
|
|
|
|
self._write(self.framer.inv(items));
|
|
});
|
|
};
|
|
|
|
Peer.prototype._handleGetData = function handleGetData(items) {
|
|
var self = this;
|
|
var check = [];
|
|
var notfound = [];
|
|
var hash, entry, isWitness, value, i, item;
|
|
|
|
if (items.length > 50000)
|
|
return this._error('message getdata size() = %d', items.length);
|
|
|
|
for (i = 0; i < items.length; i++) {
|
|
item = items[i];
|
|
|
|
hash = utils.toHex(item.hash);
|
|
entry = this._broadcast.map[hash];
|
|
isWitness = item.type & constants.invWitnessMask;
|
|
value = null;
|
|
|
|
if (!entry) {
|
|
check.push(item);
|
|
continue;
|
|
}
|
|
|
|
if ((item.type & ~constants.invWitnessMask) !== entry.type) {
|
|
utils.debug(
|
|
'Peer %s requested an existing item with the wrong type.',
|
|
this.host);
|
|
continue;
|
|
}
|
|
|
|
utils.debug(
|
|
'Peer %s requested %s:%s as a %s packet.',
|
|
this.host,
|
|
entry.packetType,
|
|
utils.revHex(utils.toHex(entry.hash)),
|
|
isWitness ? 'witness' : 'normal');
|
|
|
|
if (isWitness)
|
|
this._write(this.framer.packet(entry.packetType, entry.witnessValue));
|
|
else
|
|
this._write(this.framer.packet(entry.packetType, entry.value));
|
|
|
|
entry.e.emit('request');
|
|
}
|
|
|
|
if (this.pool.options.selfish)
|
|
return;
|
|
|
|
utils.forEachSerial(check, function(item, next) {
|
|
var isWitness = item.type & constants.invWitnessMask;
|
|
var type = item.type & ~constants.invWitnessMask;
|
|
var hash = utils.toHex(item.hash);
|
|
var data;
|
|
|
|
if (type === constants.inv.tx) {
|
|
if (!self.mempool) {
|
|
notfound.push({ type: constants.inv.tx, hash: hash });
|
|
return next();
|
|
}
|
|
return self.mempool.getTX(hash, function(err, tx) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (!tx) {
|
|
notfound.push({ type: constants.inv.tx, hash: hash });
|
|
return next();
|
|
}
|
|
|
|
if (isWitness)
|
|
data = tx.renderWitness();
|
|
else
|
|
data = tx.renderNormal();
|
|
|
|
self._write(self.framer.packet('tx', data));
|
|
|
|
next();
|
|
});
|
|
}
|
|
|
|
if (type === constants.inv.block) {
|
|
if (self.chain.db.options.spv) {
|
|
notfound.push({ type: constants.inv.block, hash: hash });
|
|
return next();
|
|
}
|
|
if (self.chain.db.prune) {
|
|
notfound.push({ type: constants.inv.block, hash: hash });
|
|
return;
|
|
}
|
|
return self.chain.db.getBlock(hash, function(err, block) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (!block) {
|
|
notfound.push({ type: constants.inv.block, hash: hash });
|
|
return next();
|
|
}
|
|
|
|
if (isWitness)
|
|
data = block.renderWitness();
|
|
else
|
|
data = block.renderNormal();
|
|
|
|
self._write(self.framer.packet('block', data));
|
|
|
|
if (hash === self.hashContinue) {
|
|
self._write(self.framer.inv([{
|
|
type: constants.inv.block,
|
|
hash: self.chain.tip.hash
|
|
}]));
|
|
self.hashContinue = null;
|
|
}
|
|
|
|
next();
|
|
});
|
|
}
|
|
|
|
if (type === constants.inv.filteredblock) {
|
|
if (self.chain.db.options.spv) {
|
|
notfound.push({ type: constants.inv.block, hash: hash });
|
|
return next();
|
|
}
|
|
if (self.chain.db.prune) {
|
|
notfound.push({ type: constants.inv.block, hash: hash });
|
|
return;
|
|
}
|
|
return self.chain.db.getBlock(hash, function(err, block) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (!block) {
|
|
notfound.push({ type: constants.inv.block, hash: hash });
|
|
return next();
|
|
}
|
|
|
|
block = block.toMerkle(self.filter);
|
|
|
|
self._write(self.framer.merkleBlock(block));
|
|
|
|
block.txs.forEach(function(tx) {
|
|
if (isWitness)
|
|
tx = tx.renderWitness();
|
|
else
|
|
tx = tx.renderNormal();
|
|
|
|
self._write(self.framer.packet('tx', tx));
|
|
});
|
|
|
|
if (hash === self.hashContinue) {
|
|
self._write(self.framer.inv([{
|
|
type: constants.inv.block,
|
|
hash: self.chain.tip.hash
|
|
}]));
|
|
self.hashContinue = null;
|
|
}
|
|
|
|
next();
|
|
});
|
|
}
|
|
|
|
notfound.push({ type: type, hash: hash });
|
|
return next();
|
|
}, function(err) {
|
|
if (err)
|
|
self.emit('error', err);
|
|
|
|
utils.debug(
|
|
'Served %d items to %s with getdata (notfound=%d).',
|
|
items.length - notfound.length,
|
|
notfound.length,
|
|
self.host);
|
|
|
|
if (notfound.length > 0)
|
|
self._write(self.framer.notFound(notfound));
|
|
});
|
|
};
|
|
|
|
Peer.prototype._handleAddr = function handleAddr(addrs) {
|
|
var now = utils.now();
|
|
var i, addr, ts, host;
|
|
|
|
for (i = 0; i < addrs.length; i++) {
|
|
addr = addrs[i];
|
|
|
|
ts = addr.ts;
|
|
host = addr.ipv4 !== '0.0.0.0'
|
|
? addr.ipv4
|
|
: addr.ipv6;
|
|
|
|
if (ts <= 100000000 || ts > now + 10 * 60)
|
|
ts = now - 5 * 24 * 60 * 60;
|
|
|
|
this.emit('addr', {
|
|
ts: ts,
|
|
services: addr.services,
|
|
host: host,
|
|
port: addr.port || network.port,
|
|
network: addr.network,
|
|
bloom: addr.bloom,
|
|
getutxo: addr.getutxo,
|
|
witness: addr.witness,
|
|
headers: addr.version >= 31800,
|
|
spv: addr.bloom && addr.version >= 70011
|
|
});
|
|
}
|
|
|
|
utils.debug(
|
|
'Recieved %d peers (seeds=%d, peers=%d).',
|
|
addrs.length,
|
|
this.pool.seeds.length,
|
|
this.pool.peers.all.length);
|
|
};
|
|
|
|
Peer.prototype._handlePing = function handlePing(data) {
|
|
this._write(this.framer.pong({
|
|
nonce: data.nonce
|
|
}));
|
|
this.emit('ping', data);
|
|
};
|
|
|
|
Peer.prototype._handlePong = function handlePong(data) {
|
|
if (!this.challenge || this.challenge.cmp(data.nonce) !== 0)
|
|
return this.emit('pong', false);
|
|
|
|
this.lastPong = utils.now();
|
|
|
|
return this.emit('pong', true);
|
|
};
|
|
|
|
Peer.prototype._handleGetAddr = function handleGetAddr() {
|
|
var hosts = {};
|
|
var peers = this.pool.peers.all;
|
|
var items = [];
|
|
var i, peer, ip, version;
|
|
|
|
if (this.pool.options.selfish)
|
|
return;
|
|
|
|
for (i = 0; i < peers.length; i++) {
|
|
peer = peers[i];
|
|
|
|
if (!peer.socket || !peer.socket.remoteAddress)
|
|
continue;
|
|
|
|
ip = peer.socket.remoteAddress;
|
|
version = utils.isIP(ip);
|
|
|
|
if (!version)
|
|
continue;
|
|
|
|
if (hosts[ip])
|
|
continue;
|
|
|
|
hosts[ip] = true;
|
|
|
|
items.push({
|
|
ts: peer.ts,
|
|
services: peer.version ? peer.version.services : null,
|
|
ipv4: version === 4 ? ip : null,
|
|
ipv6: version === 6 ? ip : null,
|
|
port: peer.socket.remotePort || network.port
|
|
});
|
|
}
|
|
|
|
return this._write(this.framer.addr(peers));
|
|
};
|
|
|
|
Peer.prototype._handleInv = function handleInv(items) {
|
|
var blocks = [];
|
|
var txs = [];
|
|
var item, i;
|
|
|
|
this.emit('inv', items);
|
|
|
|
for (i = 0; i < items.length; i++) {
|
|
item = items[i];
|
|
if (item.type === constants.inv.tx)
|
|
txs.push(item.hash);
|
|
else if (item.type === constants.inv.block)
|
|
blocks.push(item.hash);
|
|
}
|
|
|
|
if (blocks.length > 0)
|
|
this.emit('blocks', blocks);
|
|
|
|
if (txs.length > 0)
|
|
this.emit('txs', txs);
|
|
};
|
|
|
|
Peer.prototype._handleHeaders = function handleHeaders(headers) {
|
|
headers = headers.map(function(header) {
|
|
return new bcoin.headers(header);
|
|
});
|
|
|
|
this.emit('headers', headers);
|
|
};
|
|
|
|
Peer.prototype._handleReject = function handleReject(payload) {
|
|
var hash, entry;
|
|
|
|
this.emit('reject', payload);
|
|
|
|
if (!payload.data)
|
|
return;
|
|
|
|
hash = utils.toHex(payload.data);
|
|
entry = this._broadcast.map[hash];
|
|
|
|
if (!entry)
|
|
return;
|
|
|
|
entry.e.emit('reject', payload);
|
|
};
|
|
|
|
Peer.prototype._handleAlert = function handleAlert(details) {
|
|
var hash = utils.dsha256(details.payload);
|
|
var signature = details.signature;
|
|
|
|
if (!bcoin.ec.verify(hash, signature, network.alertKey)) {
|
|
utils.debug('Peer %s sent a phony alert packet.', this.host);
|
|
// Let's look at it because why not?
|
|
utils.debug(details);
|
|
this.setMisbehavior(100);
|
|
return;
|
|
}
|
|
|
|
this.emit('alert', details);
|
|
};
|
|
|
|
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.chain._getCachedHeight(hashes[0]),
|
|
hashes ? utils.revHex(hashes[0]) : 0,
|
|
stop ? utils.revHex(stop) : 0);
|
|
|
|
this._write(this.framer.getHeaders(hashes, stop));
|
|
};
|
|
|
|
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.chain._getCachedHeight(hashes[0]),
|
|
hashes ? utils.revHex(hashes[0]) : 0,
|
|
stop ? utils.revHex(stop) : 0);
|
|
|
|
this._write(this.framer.getBlocks(hashes, stop));
|
|
};
|
|
|
|
Peer.prototype.getMempool = function getMempool() {
|
|
utils.debug(
|
|
'Requesting inv packet from %s with mempool',
|
|
this.host);
|
|
|
|
this._write(this.framer.mempool());
|
|
};
|
|
|
|
Peer.prototype.reject = function reject(details) {
|
|
utils.debug(
|
|
'Sending reject packet to %s',
|
|
this.host);
|
|
|
|
this._write(this.framer.reject(details));
|
|
};
|
|
|
|
Peer.prototype.isMisbehaving = function isMisbehaving() {
|
|
return this.pool.isMisbehaving(this.host);
|
|
};
|
|
|
|
Peer.prototype.setMisbehavior = function setMisbehavior(score) {
|
|
return this.pool.setMisbehavior(this, score);
|
|
};
|
|
|
|
Peer.prototype.sendReject = function sendReject(obj, code, reason, score) {
|
|
return this.pool.reject(this, obj, code, reason, score);
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Peer;
|