Remove old files
This commit is contained in:
parent
8eca7285e0
commit
30740be5df
|
@ -1,576 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var MAX_RECEIVE_BUFFER = 10000000;
|
|
||||||
var PROTOCOL_VERSION = 70000;
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
var Put = require('bufferput');
|
|
||||||
var Buffers = require('buffers');
|
|
||||||
var buffertools = require('buffertools');
|
|
||||||
|
|
||||||
// PATCH TODO: Remove (yemel)
|
|
||||||
Buffers.prototype.skip = function (i) {
|
|
||||||
if (i == 0) {
|
|
||||||
return;
|
|
||||||
} else if (i == this.length) {
|
|
||||||
this.buffers = [];
|
|
||||||
this.length = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var pos = this.pos(i);
|
|
||||||
this.buffers = this.buffers.slice(pos.buf);
|
|
||||||
this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset));
|
|
||||||
this.length -= i;
|
|
||||||
};
|
|
||||||
|
|
||||||
var networks = require('../networks');
|
|
||||||
var Block = require('../block');
|
|
||||||
var Transaction = require('../transaction');
|
|
||||||
var BufferUtil = require('../util/buffer');
|
|
||||||
var BufferReader = require('../encoding/bufferreader');
|
|
||||||
var Hash = require('../crypto/hash');
|
|
||||||
var Random = require('../crypto/random');
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
|
|
||||||
var nonce = Random.getPseudoRandomBuffer(8);
|
|
||||||
|
|
||||||
var BIP0031_VERSION = 60000;
|
|
||||||
|
|
||||||
function Connection(socket, peer, opts) {
|
|
||||||
this.config = opts || {};
|
|
||||||
|
|
||||||
this.network = this.config.network || networks.livenet;
|
|
||||||
this.socket = socket;
|
|
||||||
this.peer = peer;
|
|
||||||
|
|
||||||
// check for socks5 proxy options and construct a proxied socket
|
|
||||||
if (this.config.proxy) {
|
|
||||||
var Socks5Client = require('socks5-client');
|
|
||||||
this.socket = new Socks5Client(this.config.proxy.host, this.config.proxy.port);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A connection is considered "active" once we have received verack
|
|
||||||
this.active = false;
|
|
||||||
// The version incoming packages are interpreted as
|
|
||||||
this.recvVer = 0;
|
|
||||||
// The version outgoing packages are sent as
|
|
||||||
this.sendVer = 0;
|
|
||||||
// The (claimed) height of the remote peer's block chain
|
|
||||||
this.bestHeight = 0;
|
|
||||||
// Is this an inbound connection?
|
|
||||||
this.inbound = !!this.socket.server;
|
|
||||||
// Have we sent a getaddr on this connection?
|
|
||||||
this.getaddr = false;
|
|
||||||
|
|
||||||
// Receive buffer
|
|
||||||
this.buffers = new Buffers();
|
|
||||||
|
|
||||||
// Starting 20 Feb 2012, Version 0.2 is obsolete
|
|
||||||
// This is the same behavior as the official client
|
|
||||||
if (new Date().getTime() > 1329696000000) {
|
|
||||||
this.recvVer = 209;
|
|
||||||
this.sendVer = 209;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setupHandlers();
|
|
||||||
}
|
|
||||||
util.inherits(Connection, EventEmitter);
|
|
||||||
|
|
||||||
Connection.prototype.open = function(callback) {
|
|
||||||
if (typeof callback === 'function') this.once('connect', callback);
|
|
||||||
this.socket.connect(this.peer.port, this.peer.host);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.setupHandlers = function() {
|
|
||||||
this.socket.addListener('connect', this.handleConnect.bind(this));
|
|
||||||
this.socket.addListener('error', this.handleError.bind(this));
|
|
||||||
this.socket.addListener('end', this.handleDisconnect.bind(this));
|
|
||||||
this.socket.addListener('data', (function(data) {
|
|
||||||
var dumpLen = 35;
|
|
||||||
console.debug('[' + this.peer + '] ' +
|
|
||||||
'Recieved ' + data.length + ' bytes of data:');
|
|
||||||
console.debug('... ' + buffertools.toHex(data.slice(0, dumpLen > data.length ?
|
|
||||||
data.length : dumpLen)) +
|
|
||||||
(data.length > dumpLen ? '...' : ''));
|
|
||||||
}).bind(this));
|
|
||||||
this.socket.addListener('data', this.handleData.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.handleConnect = function() {
|
|
||||||
if (!this.inbound) {
|
|
||||||
this.sendVersion();
|
|
||||||
}
|
|
||||||
this.emit('connect', {
|
|
||||||
conn: this,
|
|
||||||
socket: this.socket,
|
|
||||||
peer: this.peer
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.handleError = function(err) {
|
|
||||||
if (err.errno == 110 || err.errno == 'ETIMEDOUT') {
|
|
||||||
console.info('connection timed out for ' + this.peer);
|
|
||||||
} else if (err.errno == 111 || err.errno == 'ECONNREFUSED') {
|
|
||||||
console.info('connection refused for ' + this.peer);
|
|
||||||
} else {
|
|
||||||
console.warn('connection with ' + this.peer + ' ' + err.toString());
|
|
||||||
}
|
|
||||||
this.emit('error', {
|
|
||||||
conn: this,
|
|
||||||
socket: this.socket,
|
|
||||||
peer: this.peer,
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.handleDisconnect = function() {
|
|
||||||
this.emit('disconnect', {
|
|
||||||
conn: this,
|
|
||||||
socket: this.socket,
|
|
||||||
peer: this.peer
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.handleMessage = function(message) {
|
|
||||||
if (!message) {
|
|
||||||
// Parser was unable to make sense of the message, drop it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (message.command) {
|
|
||||||
case 'version':
|
|
||||||
// Did we connect to ourself?
|
|
||||||
if (buffertools.compare(nonce, message.nonce) === 0) {
|
|
||||||
this.socket.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.inbound) {
|
|
||||||
this.sendVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.version >= 209) {
|
|
||||||
this.sendMessage('verack', new Buffer([]));
|
|
||||||
}
|
|
||||||
this.sendVer = Math.min(message.version, PROTOCOL_VERSION);
|
|
||||||
if (message.version < 209) {
|
|
||||||
this.recvVer = Math.min(message.version, PROTOCOL_VERSION);
|
|
||||||
} else {
|
|
||||||
// We won't start expecting a checksum until after we've received
|
|
||||||
// the 'verack' message.
|
|
||||||
this.once('verack', (function() {
|
|
||||||
this.recvVer = message.version;
|
|
||||||
}).bind(this));
|
|
||||||
}
|
|
||||||
this.bestHeight = message.start_height;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'verack':
|
|
||||||
this.recvVer = Math.min(message.version, PROTOCOL_VERSION);
|
|
||||||
this.active = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ping':
|
|
||||||
if ('object' === typeof message.nonce) {
|
|
||||||
this.sendPong(message.nonce);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.err('Error while handling "' + message.command + '" message from ' +
|
|
||||||
this.peer + ':\n' +
|
|
||||||
(e.stack ? e.stack : e.toString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.emit(message.command, {
|
|
||||||
conn: this,
|
|
||||||
socket: this.socket,
|
|
||||||
peer: this.peer,
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendPong = function(nonce) {
|
|
||||||
this.sendMessage('pong', nonce);
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendVersion = function() {
|
|
||||||
var subversion = '/BitcoinX:0.1/';
|
|
||||||
|
|
||||||
var put = new Put();
|
|
||||||
put.word32le(PROTOCOL_VERSION); // version
|
|
||||||
put.word64le(1); // services
|
|
||||||
put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
|
|
||||||
put.pad(26); // addr_me
|
|
||||||
put.pad(26); // addr_you
|
|
||||||
put.put(nonce);
|
|
||||||
put.varint(subversion.length);
|
|
||||||
put.put(new Buffer(subversion, 'ascii'));
|
|
||||||
put.word32le(0);
|
|
||||||
|
|
||||||
this.sendMessage('version', put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendGetBlocks = function(starts, stop, wantHeaders) {
|
|
||||||
// Default value for stop is 0 to get as many blocks as possible (500)
|
|
||||||
stop = stop || BufferUtil.NULL_HASH;
|
|
||||||
|
|
||||||
var put = new Put();
|
|
||||||
|
|
||||||
// https://en.bitcoin.it/wiki/Protocol_specification#getblocks
|
|
||||||
put.word32le(this.sendVer);
|
|
||||||
put.varint(starts.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < starts.length; i++) {
|
|
||||||
if (starts[i].length != 32) {
|
|
||||||
throw new Error('Invalid hash length');
|
|
||||||
}
|
|
||||||
|
|
||||||
put.put(starts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stopBuffer = new Buffer(stop, 'binary');
|
|
||||||
if (stopBuffer.length != 32) {
|
|
||||||
throw new Error('Invalid hash length');
|
|
||||||
}
|
|
||||||
|
|
||||||
put.put(stopBuffer);
|
|
||||||
|
|
||||||
var command = 'getblocks';
|
|
||||||
if (wantHeaders)
|
|
||||||
command = 'getheaders';
|
|
||||||
this.sendMessage(command, put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendGetHeaders = function(starts, stop) {
|
|
||||||
this.sendGetBlocks(starts, stop, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendGetData = function(invs) {
|
|
||||||
var put = new Put();
|
|
||||||
put.varint(invs.length);
|
|
||||||
for (var i = 0; i < invs.length; i++) {
|
|
||||||
put.word32le(invs[i].type);
|
|
||||||
put.put(invs[i].hash);
|
|
||||||
}
|
|
||||||
this.sendMessage('getdata', put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendGetAddr = function(invs) {
|
|
||||||
var put = new Put();
|
|
||||||
this.sendMessage('getaddr', put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendInv = function(data) {
|
|
||||||
if (!Array.isArray(data)) data = [data];
|
|
||||||
var put = new Put();
|
|
||||||
put.varint(data.length);
|
|
||||||
data.forEach(function(value) {
|
|
||||||
if (value instanceof Block) {
|
|
||||||
// Block
|
|
||||||
put.word32le(2); // MSG_BLOCK
|
|
||||||
} else {
|
|
||||||
// Transaction
|
|
||||||
put.word32le(1); // MSG_TX
|
|
||||||
}
|
|
||||||
put.put(value.getHash());
|
|
||||||
});
|
|
||||||
this.sendMessage('inv', put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendHeaders = function(headers) {
|
|
||||||
var put = new Put();
|
|
||||||
put.varint(headers.length);
|
|
||||||
headers.forEach(function(header) {
|
|
||||||
put.put(header);
|
|
||||||
|
|
||||||
// Indicate 0 transactions
|
|
||||||
put.word8(0);
|
|
||||||
});
|
|
||||||
this.sendMessage('headers', put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendTx = function(tx) {
|
|
||||||
this.sendMessage('tx', tx.serialize());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendBlock = function(block, txs) {
|
|
||||||
var put = new Put();
|
|
||||||
|
|
||||||
// Block header
|
|
||||||
put.put(block.getHeader());
|
|
||||||
|
|
||||||
// List of transactions
|
|
||||||
put.varint(txs.length);
|
|
||||||
txs.forEach(function(tx) {
|
|
||||||
put.put(tx.serialize());
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sendMessage('block', put.buffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.sendMessage = function(command, payload) {
|
|
||||||
try {
|
|
||||||
var magic = this.network.magic;
|
|
||||||
var commandBuf = new Buffer(command, 'ascii');
|
|
||||||
if (commandBuf.length > 12) throw 'Command name too long';
|
|
||||||
|
|
||||||
var checksum;
|
|
||||||
if (this.sendVer >= 209) {
|
|
||||||
checksum = Hash.sha256sha256(payload).slice(0, 4);
|
|
||||||
} else {
|
|
||||||
checksum = new Buffer([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = new Put(); // -- HEADER --
|
|
||||||
message.put(magic); // magic bytes
|
|
||||||
message.put(commandBuf); // command name
|
|
||||||
message.pad(12 - commandBuf.length); // zero-padded
|
|
||||||
message.word32le(payload.length); // payload length
|
|
||||||
message.put(checksum); // checksum
|
|
||||||
// -- BODY --
|
|
||||||
message.put(payload); // payload data
|
|
||||||
|
|
||||||
var buffer = message.buffer();
|
|
||||||
|
|
||||||
console.debug('[' + this.peer + '] ' +
|
|
||||||
'Sending message ' + command + ' (' + payload.length + ' bytes)');
|
|
||||||
|
|
||||||
this.socket.write(buffer);
|
|
||||||
} catch (err) {
|
|
||||||
// TODO: We should catch this error one level higher in order to better
|
|
||||||
// determine how to react to it. For now though, ignoring it will do.
|
|
||||||
console.err('Error while sending message to peer ' + this.peer + ': ' +
|
|
||||||
(err.stack ? err.stack : err.toString()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.handleData = function(data) {
|
|
||||||
this.buffers.push(data);
|
|
||||||
|
|
||||||
if (this.buffers.length > MAX_RECEIVE_BUFFER) {
|
|
||||||
console.err('Peer ' + this.peer + ' exceeded maxreceivebuffer, disconnecting.' +
|
|
||||||
(err.stack ? err.stack : err.toString()));
|
|
||||||
this.socket.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processData();
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.processData = function() {
|
|
||||||
// If there are less than 20 bytes there can't be a message yet.
|
|
||||||
if (this.buffers.length < 20) return;
|
|
||||||
|
|
||||||
var magic = this.network.magic;
|
|
||||||
var i = 0;
|
|
||||||
for (;;) {
|
|
||||||
if (this.buffers.get(i) === magic[0] &&
|
|
||||||
this.buffers.get(i + 1) === magic[1] &&
|
|
||||||
this.buffers.get(i + 2) === magic[2] &&
|
|
||||||
this.buffers.get(i + 3) === magic[3]) {
|
|
||||||
if (i !== 0) {
|
|
||||||
console.debug('[' + this.peer + '] ' +
|
|
||||||
'Received ' + i +
|
|
||||||
' bytes of inter-message garbage: ');
|
|
||||||
console.debug('... ' + this.buffers.slice(0, i));
|
|
||||||
|
|
||||||
this.buffers.skip(i);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > (this.buffers.length - 4)) {
|
|
||||||
this.buffers.skip(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var payloadLen = (this.buffers.get(16)) +
|
|
||||||
(this.buffers.get(17) << 8) +
|
|
||||||
(this.buffers.get(18) << 16) +
|
|
||||||
(this.buffers.get(19) << 24);
|
|
||||||
|
|
||||||
var startPos = (this.recvVer >= 209) ? 24 : 20;
|
|
||||||
var endPos = startPos + payloadLen;
|
|
||||||
|
|
||||||
if (this.buffers.length < endPos) return;
|
|
||||||
|
|
||||||
var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/, '');
|
|
||||||
var payload = this.buffers.slice(startPos, endPos);
|
|
||||||
var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null;
|
|
||||||
|
|
||||||
console.debug('[' + this.peer + '] ' +
|
|
||||||
'Received message ' + command +
|
|
||||||
' (' + payloadLen + ' bytes)');
|
|
||||||
|
|
||||||
if (checksum !== null) {
|
|
||||||
var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4);
|
|
||||||
if (buffertools.compare(checksumConfirm, checksum) !== 0) {
|
|
||||||
console.err('[' + this.peer + '] ' +
|
|
||||||
'Checksum failed', {
|
|
||||||
cmd: command,
|
|
||||||
expected: checksumConfirm.toString('hex'),
|
|
||||||
actual: checksum.toString('hex')
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var message;
|
|
||||||
try {
|
|
||||||
message = this.parseMessage(command, payload);
|
|
||||||
} catch (e) {
|
|
||||||
console.err('Error while parsing message ' + command + ' from ' +
|
|
||||||
this.peer + ':\n' +
|
|
||||||
(e.stack ? e.stack : e.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
this.handleMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buffers.skip(endPos);
|
|
||||||
this.processData();
|
|
||||||
};
|
|
||||||
|
|
||||||
Connection.prototype.parseMessage = function(command, payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
command: command
|
|
||||||
};
|
|
||||||
|
|
||||||
var i;
|
|
||||||
|
|
||||||
switch (command) {
|
|
||||||
case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version
|
|
||||||
data.version = parser.readUInt32LE();
|
|
||||||
data.services = parser.readUInt64LEBN();
|
|
||||||
data.timestamp = parser.readUInt64LEBN();
|
|
||||||
data.addr_me = parser.read(26);
|
|
||||||
data.addr_you = parser.read(26);
|
|
||||||
data.nonce = parser.read(8);
|
|
||||||
data.subversion = parser.readVarintBuf();
|
|
||||||
data.start_height = parser.readUInt32LE();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'inv':
|
|
||||||
case 'getdata':
|
|
||||||
data.count = parser.readVarintNum();
|
|
||||||
|
|
||||||
data.invs = [];
|
|
||||||
for (i = 0; i < data.count; i++) {
|
|
||||||
data.invs.push({
|
|
||||||
type: parser.readUInt32LE(),
|
|
||||||
hash: parser.read(32)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'headers':
|
|
||||||
data.count = parser.readVarintNum();
|
|
||||||
|
|
||||||
data.headers = [];
|
|
||||||
for (i = 0; i < data.count; i++) {
|
|
||||||
var header = Block.fromBufferReader(parser);
|
|
||||||
data.headers.push(header);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'block':
|
|
||||||
var block = Block.fromBufferReader(parser);
|
|
||||||
|
|
||||||
data.block = block;
|
|
||||||
data.version = block.version;
|
|
||||||
data.prev_hash = block.prev_hash;
|
|
||||||
data.merkle_root = block.merkle_root;
|
|
||||||
data.timestamp = block.timestamp;
|
|
||||||
data.bits = block.bits;
|
|
||||||
data.nonce = block.nonce;
|
|
||||||
|
|
||||||
data.txs = block.txs;
|
|
||||||
|
|
||||||
data.size = payload.length;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tx':
|
|
||||||
var tx = Transaction.fromBufferReader(parser);
|
|
||||||
return {
|
|
||||||
command: command,
|
|
||||||
version: tx.version,
|
|
||||||
lock_time: tx.lock_time,
|
|
||||||
ins: tx.ins,
|
|
||||||
outs: tx.outs,
|
|
||||||
tx: tx,
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'getblocks':
|
|
||||||
case 'getheaders':
|
|
||||||
// parse out the version
|
|
||||||
data.version = parser.readUInt32LE();
|
|
||||||
|
|
||||||
// TODO: Limit block locator size?
|
|
||||||
// reference implementation limits to 500 results
|
|
||||||
var startCount = parser.readVarintNum();
|
|
||||||
|
|
||||||
data.starts = [];
|
|
||||||
for (i = 0; i < startCount; i++) {
|
|
||||||
data.starts.push(parser.read(32));
|
|
||||||
}
|
|
||||||
data.stop = parser.read(32);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'addr':
|
|
||||||
var addrCount = parser.readVarintNum();
|
|
||||||
|
|
||||||
// Enforce a maximum number of addresses per message
|
|
||||||
if (addrCount > 1000) {
|
|
||||||
addrCount = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.addrs = [];
|
|
||||||
for (i = 0; i < addrCount; i++) {
|
|
||||||
// TODO: Time actually depends on the version of the other peer (>=31402)
|
|
||||||
data.addrs.push({
|
|
||||||
time: parser.readUInt32LE(),
|
|
||||||
services: parser.readUInt64LEBN(),
|
|
||||||
ip: parser.read(16),
|
|
||||||
port: parser.readUInt16BE()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'alert':
|
|
||||||
data.payload = parser.readVarintBuf();
|
|
||||||
data.signature = parser.readVarintBuf();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ping':
|
|
||||||
if (this.recvVer > BIP0031_VERSION) {
|
|
||||||
data.nonce = parser.read(8);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'getaddr':
|
|
||||||
case 'verack':
|
|
||||||
case 'reject':
|
|
||||||
// Empty message, nothing to parse
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.err('Connection.parseMessage(): Command not implemented', {
|
|
||||||
cmd: command
|
|
||||||
});
|
|
||||||
|
|
||||||
// This tells the calling function not to issue an event
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Connection;
|
|
|
@ -1,56 +0,0 @@
|
||||||
var Net = require('net');
|
|
||||||
var Put = require('bufferput');
|
|
||||||
var buffertools = require('buffertools');
|
|
||||||
|
|
||||||
function Peer(host, port, services) {
|
|
||||||
if ("string" === typeof host) {
|
|
||||||
if (host.indexOf(':') && !port) {
|
|
||||||
var parts = host.split(':');
|
|
||||||
host = parts[0];
|
|
||||||
port = parts[1];
|
|
||||||
}
|
|
||||||
this.host = host;
|
|
||||||
this.port = +port || 8333;
|
|
||||||
} else if (host instanceof Peer) {
|
|
||||||
this.host = host.host;
|
|
||||||
this.port = host.port;
|
|
||||||
} else if (Buffer.isBuffer(host)) {
|
|
||||||
if (buffertools.compare(Peer.IPV6_IPV4_PADDING, host.slice(0, 12)) != 0) {
|
|
||||||
throw new Error('IPV6 not supported yet! Cannot instantiate host.');
|
|
||||||
}
|
|
||||||
this.host = Array.prototype.slice.apply(host.slice(12)).join('.');
|
|
||||||
this.port = +port || 8333;
|
|
||||||
} else {
|
|
||||||
throw new Error('Could not instantiate peer, invalid parameter type: ' +
|
|
||||||
typeof host);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.services = (services) ? services : null;
|
|
||||||
this.lastSeen = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Peer.IPV6_IPV4_PADDING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
|
|
||||||
|
|
||||||
Peer.prototype.createConnection = function() {
|
|
||||||
this.connection = Net.createConnection(this.port, this.host);
|
|
||||||
return this.connection;
|
|
||||||
};
|
|
||||||
|
|
||||||
Peer.prototype.getHostAsBuffer = function() {
|
|
||||||
return new Buffer(this.host.split('.'));
|
|
||||||
};
|
|
||||||
|
|
||||||
Peer.prototype.toString = function() {
|
|
||||||
return this.host + ":" + this.port;
|
|
||||||
};
|
|
||||||
|
|
||||||
Peer.prototype.toBuffer = function() {
|
|
||||||
var put = new Put();
|
|
||||||
put.word32le(this.lastSeen);
|
|
||||||
put.word64le(this.services);
|
|
||||||
put.put(this.getHostAsBuffer());
|
|
||||||
put.word16be(this.port);
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Peer;
|
|
|
@ -1,313 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var dns = require('dns');
|
|
||||||
var util = require('util');
|
|
||||||
var async = require('async');
|
|
||||||
|
|
||||||
var Connection = require('./connection');
|
|
||||||
var Peer = require('./peer');
|
|
||||||
var networks = require('../networks');
|
|
||||||
|
|
||||||
var GetAdjustedTime = function() {
|
|
||||||
// TODO: Implement actual adjustment
|
|
||||||
return Math.floor(new Date().getTime() / 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
function PeerManager(config) {
|
|
||||||
// extend defaults with config
|
|
||||||
this.config = config || {};
|
|
||||||
this.config.network = this.config.network || networks.livenet;
|
|
||||||
|
|
||||||
this.active = false;
|
|
||||||
this.timer = null;
|
|
||||||
|
|
||||||
this.peers = [];
|
|
||||||
this.pool = [];
|
|
||||||
this.connections = [];
|
|
||||||
this.isConnected = false;
|
|
||||||
this.peerDiscovery = false;
|
|
||||||
|
|
||||||
// Move these to the Node's settings object
|
|
||||||
this.interval = 5000;
|
|
||||||
this.minConnections = 8;
|
|
||||||
this.minKnownPeers = 10;
|
|
||||||
|
|
||||||
// keep track of tried seeds and results
|
|
||||||
this.seeds = {
|
|
||||||
resolved: [],
|
|
||||||
failed: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
util.inherits(PeerManager, EventEmitter);
|
|
||||||
PeerManager.Connection = Connection;
|
|
||||||
|
|
||||||
PeerManager.prototype.start = function() {
|
|
||||||
this.active = true;
|
|
||||||
if (!this.timer) {
|
|
||||||
this.timer = setInterval(this.checkStatus.bind(this), this.interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.stop = function() {
|
|
||||||
this.active = false;
|
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
for (var i = 0; i < this.connections.length; i++) {
|
|
||||||
this.connections[i].socket.end();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.addPeer = function(peer, port) {
|
|
||||||
if (peer instanceof Peer) {
|
|
||||||
this.peers.push(peer);
|
|
||||||
} else if ("string" == typeof peer) {
|
|
||||||
this.addPeer(new Peer(peer, port));
|
|
||||||
} else {
|
|
||||||
console.error('Node.addPeer(): Invalid value provided for peer', {
|
|
||||||
val: peer
|
|
||||||
});
|
|
||||||
throw 'Node.addPeer(): Invalid value provided for peer.';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.removePeer = function(peer) {
|
|
||||||
var index = this.peers.indexOf(peer);
|
|
||||||
var exists = !!~index;
|
|
||||||
if (exists) this.peers.splice(index, 1);
|
|
||||||
return exists;
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.checkStatus = function checkStatus() {
|
|
||||||
// Make sure we are connected to all forcePeers
|
|
||||||
if (this.peers.length) {
|
|
||||||
var peerIndex = {};
|
|
||||||
this.peers.forEach(function(peer) {
|
|
||||||
peerIndex[peer.toString()] = peer;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ignore the ones we're already connected to
|
|
||||||
this.connections.forEach(function(conn) {
|
|
||||||
var peerName = conn.peer.toString();
|
|
||||||
if ("undefined" !== peerIndex[peerName]) {
|
|
||||||
delete peerIndex[peerName];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// for debug purposes, print how many of our peers are actually connected
|
|
||||||
var connected = 0
|
|
||||||
this.peers.forEach(function(p) {
|
|
||||||
if (p.connection && !p.connection._connecting) connected++
|
|
||||||
});
|
|
||||||
console.debug(connected + ' of ' + this.peers.length + ' peers connected');
|
|
||||||
|
|
||||||
Object.keys(peerIndex).forEach(function(i) {
|
|
||||||
this.connectTo(peerIndex[i]);
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.connectTo = function(peer) {
|
|
||||||
console.info('connecting to ' + peer);
|
|
||||||
try {
|
|
||||||
return this.addConnection(peer.createConnection(), peer);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('creating connection', e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.addConnection = function(socketConn, peer) {
|
|
||||||
var conn = new Connection(socketConn, peer, this.config);
|
|
||||||
this.connections.push(conn);
|
|
||||||
this.emit('connection', conn);
|
|
||||||
|
|
||||||
conn.addListener('version', this.handleVersion.bind(this));
|
|
||||||
conn.addListener('verack', this.handleReady.bind(this));
|
|
||||||
conn.addListener('addr', this.handleAddr.bind(this));
|
|
||||||
conn.addListener('getaddr', this.handleGetAddr.bind(this));
|
|
||||||
conn.addListener('error', this.handleError.bind(this));
|
|
||||||
conn.addListener('disconnect', this.handleDisconnect.bind(this));
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.handleVersion = function(e) {
|
|
||||||
e.peer.version = e.message.version;
|
|
||||||
e.peer.start_height = e.message.start_height;
|
|
||||||
|
|
||||||
if (!e.conn.inbound) {
|
|
||||||
// TODO: Advertise our address (if listening)
|
|
||||||
}
|
|
||||||
// Get recent addresses
|
|
||||||
if (this.peerDiscovery &&
|
|
||||||
(e.message.version >= 31402 || this.peers.length < 1000)) {
|
|
||||||
e.conn.sendGetAddr();
|
|
||||||
e.conn.getaddr = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.handleReady = function(e) {
|
|
||||||
console.info('connected to ' + e.conn.peer.host + ':' + e.conn.peer.port);
|
|
||||||
this.emit('connect', {
|
|
||||||
pm: this,
|
|
||||||
conn: e.conn,
|
|
||||||
socket: e.socket,
|
|
||||||
peer: e.peer
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.isConnected == false) {
|
|
||||||
this.emit('netConnected', e);
|
|
||||||
this.isConnected = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.handleAddr = function(e) {
|
|
||||||
if (!this.peerDiscovery) return;
|
|
||||||
|
|
||||||
var now = GetAdjustedTime();
|
|
||||||
e.message.addrs.forEach(function(addr) {
|
|
||||||
try {
|
|
||||||
// In case of an invalid time, assume "5 days ago"
|
|
||||||
if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) {
|
|
||||||
addr.time = now - 5 * 24 * 60 * 60;
|
|
||||||
}
|
|
||||||
var peer = new Peer(addr.ip, addr.port, addr.services);
|
|
||||||
peer.lastSeen = addr.time;
|
|
||||||
|
|
||||||
// TODO: Handle duplicate peers
|
|
||||||
this.peers.push(peer);
|
|
||||||
|
|
||||||
// TODO: Handle addr relay
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Invalid addr received: " + e.message);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
if (e.message.addrs.length < 1000) {
|
|
||||||
e.conn.getaddr = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.handleGetAddr = function(e) {
|
|
||||||
// TODO: Reply with addr message.
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.handleError = function(e) {
|
|
||||||
console.error('unkown error with peer ' + e.peer + ' (disconnecting): ' + e.err);
|
|
||||||
this.handleDisconnect.apply(this, [].slice.call(arguments));
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.handleDisconnect = function(e) {
|
|
||||||
console.info('disconnected from peer ' + e.peer);
|
|
||||||
var i = this.connections.indexOf(e.conn);
|
|
||||||
if (i != -1) this.connections.splice(i, 1);
|
|
||||||
|
|
||||||
this.removePeer(e.peer);
|
|
||||||
if (this.pool.length) {
|
|
||||||
console.info('replacing peer using the pool of ' + this.pool.length + ' seeds');
|
|
||||||
this.addPeer(this.pool.pop());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.connections.length) {
|
|
||||||
this.emit('netDisconnected');
|
|
||||||
this.isConnected = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.getActiveConnection = function() {
|
|
||||||
var activeConnections = this.connections.filter(function(conn) {
|
|
||||||
return conn.active;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (activeConnections.length) {
|
|
||||||
var randomIndex = Math.floor(Math.random() * activeConnections.length);
|
|
||||||
var candidate = activeConnections[randomIndex];
|
|
||||||
if (candidate.socket.writable) {
|
|
||||||
return candidate;
|
|
||||||
} else {
|
|
||||||
// Socket is not writable, remove it from active connections
|
|
||||||
activeConnections.splice(randomIndex, 1);
|
|
||||||
|
|
||||||
// Then try again
|
|
||||||
// TODO: This causes an infinite recursion when all connections are dead,
|
|
||||||
// although it shouldn't.
|
|
||||||
return this.getActiveConnection();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.getActiveConnections = function() {
|
|
||||||
return this.connections.slice(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerManager.prototype.discover = function(options, callback) {
|
|
||||||
var self = this;
|
|
||||||
var seeds = this.config.network.dnsSeeds;
|
|
||||||
|
|
||||||
self.limit = options.limit || 12;
|
|
||||||
|
|
||||||
var dnsExecutor = seeds.map(function(seed) {
|
|
||||||
return function(done) {
|
|
||||||
// have we already resolved this seed?
|
|
||||||
if (~self.seeds.resolved.indexOf(seed)) {
|
|
||||||
// if so, just pass back cached peer list
|
|
||||||
return done(null, self.seeds.results[seed]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// has this seed failed to resolve?
|
|
||||||
if (~self.seeds.failed.indexOf(seed)) {
|
|
||||||
// if so, pass back empty results
|
|
||||||
return done(null, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info('resolving dns seed ' + seed);
|
|
||||||
|
|
||||||
dns.resolve(seed, function(err, peers) {
|
|
||||||
if (err) {
|
|
||||||
console.error('failed to resolve dns seed ' + seed, err);
|
|
||||||
self.seeds.failed.push(seed);
|
|
||||||
return done(null, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info('found ' + peers.length + ' peers from ' + seed);
|
|
||||||
self.seeds.resolved.push(seed);
|
|
||||||
|
|
||||||
// transform that list into a list of Peer instances
|
|
||||||
peers = peers.map(function(ip) {
|
|
||||||
return new Peer(ip, self.config.network.port);
|
|
||||||
});
|
|
||||||
|
|
||||||
peers.forEach(function(p) {
|
|
||||||
if (self.peers.length < self.limit) self.addPeer(p);
|
|
||||||
else self.pool.push(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.emit('peers', peers);
|
|
||||||
|
|
||||||
return done(null, peers);
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// try resolving all seeds
|
|
||||||
async.parallel(dnsExecutor, function(err, results) {
|
|
||||||
var peers = [];
|
|
||||||
|
|
||||||
// consolidate all resolved peers into one list
|
|
||||||
results.forEach(function(peerlist) {
|
|
||||||
peers = peers.concat(peerlist);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof callback === 'function') callback(null, peers);
|
|
||||||
});
|
|
||||||
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = PeerManager;
|
|
Loading…
Reference in New Issue