Drop code for the p2p module
This commit is contained in:
parent
dac8cdf0aa
commit
eaa07363a9
|
@ -2,8 +2,5 @@
|
||||||
* @namespace Transport
|
* @namespace Transport
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
explorers: require('./explorers'),
|
explorers: require('./explorers')
|
||||||
Messages: require('./messages'),
|
|
||||||
Peer: require('./peer'),
|
|
||||||
Pool: require('./pool')
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,613 +0,0 @@
|
||||||
'use strict';
|
|
||||||
/**
|
|
||||||
* @namespace Transport.Message
|
|
||||||
*/
|
|
||||||
/* jshint curly: false */
|
|
||||||
|
|
||||||
var Buffers = require('buffers');
|
|
||||||
var Put = require('bufferput');
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var BlockHeaderModel = require('../blockheader');
|
|
||||||
var BlockModel = require('../block');
|
|
||||||
var BufferReader = require('../encoding/bufferreader');
|
|
||||||
var BufferUtil = require('../util/buffer');
|
|
||||||
var Hash = require('../crypto/hash');
|
|
||||||
var Random = require('../crypto/random');
|
|
||||||
var TransactionModel = require('../transaction');
|
|
||||||
|
|
||||||
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
|
|
||||||
var PROTOCOL_VERSION = 70000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static helper for consuming a data buffer until the next message.
|
|
||||||
*
|
|
||||||
* @name Transport.Message#parseMessage
|
|
||||||
* @param{Network} network - the network object
|
|
||||||
* @param{Buffer} dataBuffer - the buffer to read from
|
|
||||||
* @returns{Message|undefined} A message or undefined if there is nothing to read.
|
|
||||||
*/
|
|
||||||
var parseMessage = function(network, dataBuffer) {
|
|
||||||
if (dataBuffer.length < 20) return;
|
|
||||||
|
|
||||||
// Search the next magic number
|
|
||||||
if (!discardUntilNextMessage(network, dataBuffer)) return;
|
|
||||||
|
|
||||||
var PAYLOAD_START = 16;
|
|
||||||
var payloadLen = (dataBuffer.get(PAYLOAD_START)) +
|
|
||||||
(dataBuffer.get(PAYLOAD_START + 1) << 8) +
|
|
||||||
(dataBuffer.get(PAYLOAD_START + 2) << 16) +
|
|
||||||
(dataBuffer.get(PAYLOAD_START + 3) << 24);
|
|
||||||
|
|
||||||
var messageLength = 24 + payloadLen;
|
|
||||||
if (dataBuffer.length < messageLength) return;
|
|
||||||
|
|
||||||
var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, '');
|
|
||||||
var payload = dataBuffer.slice(24, messageLength);
|
|
||||||
var checksum = dataBuffer.slice(20, 24);
|
|
||||||
|
|
||||||
var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4);
|
|
||||||
if (!BufferUtil.equals(checksumConfirm, checksum)) {
|
|
||||||
dataBuffer.skip(messageLength);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBuffer.skip(messageLength);
|
|
||||||
return Message.buildMessage(command, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.parseMessage = parseMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Internal function that discards data until founds the next message.
|
|
||||||
* @name Transport.Message#discardUntilNextMessage
|
|
||||||
*/
|
|
||||||
function discardUntilNextMessage(network, dataBuffer) {
|
|
||||||
var magicNumber = network.networkMagic;
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
for (;;) {
|
|
||||||
// check if it's the beginning of a new message
|
|
||||||
var packageNumber = dataBuffer.slice(0, 4);
|
|
||||||
if (BufferUtil.equals(packageNumber, magicNumber)) {
|
|
||||||
dataBuffer.skip(i);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// did we reach the end of the buffer?
|
|
||||||
if (i > (dataBuffer.length - 4)) {
|
|
||||||
dataBuffer.skip(i);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++; // continue scanning
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract Message that knows how to parse and serialize itself.
|
|
||||||
* Concret subclases should implement {fromBuffer} and {getPayload} methods.
|
|
||||||
* @name Transport.Message
|
|
||||||
*/
|
|
||||||
function Message() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @value
|
|
||||||
* @name Transport.Message.COMMANDS
|
|
||||||
*/
|
|
||||||
Message.COMMANDS = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up a message type by command name and instantiate the correct Message
|
|
||||||
* @name Transport.Message#buildMessage
|
|
||||||
*/
|
|
||||||
Message.buildMessage = function(command, payload) {
|
|
||||||
try {
|
|
||||||
var CommandClass = Message.COMMANDS[command];
|
|
||||||
return new CommandClass().fromBuffer(payload);
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Error while parsing message', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse instance state from buffer.
|
|
||||||
*
|
|
||||||
* @param{Buffer} payload - the buffer to read from
|
|
||||||
* @returns{Message} The same message instance
|
|
||||||
*/
|
|
||||||
Message.prototype.fromBuffer = function(payload) {
|
|
||||||
/* jshint unused: false */
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize the payload into a buffer.
|
|
||||||
*
|
|
||||||
* @returns{Buffer} the serialized payload
|
|
||||||
*/
|
|
||||||
Message.prototype.getPayload = function() {
|
|
||||||
return BufferUtil.EMPTY_BUFFER;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize the message into a buffer.
|
|
||||||
*
|
|
||||||
* @returns{Buffer} the serialized message
|
|
||||||
*/
|
|
||||||
Message.prototype.serialize = function(network) {
|
|
||||||
var magic = network.networkMagic;
|
|
||||||
var commandBuf = new Buffer(this.command, 'ascii');
|
|
||||||
if (commandBuf.length > 12) throw 'Command name too long';
|
|
||||||
|
|
||||||
var payload = this.getPayload();
|
|
||||||
var checksum = Hash.sha256sha256(payload).slice(0, 4);
|
|
||||||
|
|
||||||
// -- HEADER --
|
|
||||||
var message = new Put();
|
|
||||||
message.put(magic);
|
|
||||||
message.put(commandBuf);
|
|
||||||
message.pad(12 - commandBuf.length); // zero-padded
|
|
||||||
message.word32le(payload.length);
|
|
||||||
message.put(checksum);
|
|
||||||
|
|
||||||
// -- BODY --
|
|
||||||
message.put(payload);
|
|
||||||
|
|
||||||
return message.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Message = Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Version
|
|
||||||
* @param{string} subversion - version of the client
|
|
||||||
* @param{Buffer} nonce - a random 8 bytes buffer
|
|
||||||
*/
|
|
||||||
function Version(subversion, nonce) {
|
|
||||||
this.command = 'version';
|
|
||||||
this.version = PROTOCOL_VERSION;
|
|
||||||
this.subversion = subversion || '/BitcoinX:0.1/';
|
|
||||||
this.nonce = nonce || CONNECTION_NONCE;
|
|
||||||
}
|
|
||||||
util.inherits(Version, Message);
|
|
||||||
|
|
||||||
Version.prototype.fromBuffer = function(payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
this.version = parser.readUInt32LE();
|
|
||||||
this.services = parser.readUInt64LEBN();
|
|
||||||
this.timestamp = parser.readUInt64LEBN();
|
|
||||||
this.addr_me = parser.read(26);
|
|
||||||
this.addr_you = parser.read(26);
|
|
||||||
this.nonce = parser.read(8);
|
|
||||||
this.subversion = parser.readVarintBuf().toString();
|
|
||||||
this.start_height = parser.readUInt32LE();
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Version.prototype.getPayload = function() {
|
|
||||||
var put = new Put();
|
|
||||||
put.word32le(this.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(this.nonce);
|
|
||||||
put.varint(this.subversion.length);
|
|
||||||
put.put(new Buffer(this.subversion, 'ascii'));
|
|
||||||
put.word32le(0);
|
|
||||||
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Version = Message.COMMANDS.version = Version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inv Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Inventory
|
|
||||||
* @param{Array} inventory - reported elements
|
|
||||||
*/
|
|
||||||
function Inventory(inventory) {
|
|
||||||
this.command = 'inv';
|
|
||||||
this.inventory = inventory || [];
|
|
||||||
}
|
|
||||||
util.inherits(Inventory, Message);
|
|
||||||
|
|
||||||
Inventory.prototype.fromBuffer = function(payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
var count = parser.readVarintNum();
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
this.inventory.push({
|
|
||||||
type: parser.readUInt32LE(),
|
|
||||||
hash: parser.read(32)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Inventory.prototype.getPayload = function() {
|
|
||||||
var put = new Put();
|
|
||||||
|
|
||||||
put.varint(this.inventory.length);
|
|
||||||
this.inventory.forEach(function(value) {
|
|
||||||
put.word32le(value.type);
|
|
||||||
put.put(value.hash);
|
|
||||||
});
|
|
||||||
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Inventory = Message.COMMANDS.inv = Inventory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getdata Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.GetData
|
|
||||||
* @param{Array} inventory - requested elements
|
|
||||||
*/
|
|
||||||
function GetData(inventory) {
|
|
||||||
this.command = 'getdata';
|
|
||||||
this.inventory = inventory || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(GetData, Inventory);
|
|
||||||
module.exports.GetData = GetData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ping Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Ping
|
|
||||||
* @param{Buffer} nonce - a random 8 bytes buffer
|
|
||||||
*/
|
|
||||||
function Ping(nonce) {
|
|
||||||
this.command = 'ping';
|
|
||||||
this.nonce = nonce || CONNECTION_NONCE;
|
|
||||||
}
|
|
||||||
util.inherits(Ping, Message);
|
|
||||||
|
|
||||||
Ping.prototype.fromBuffer = function(payload) {
|
|
||||||
this.nonce = new BufferReader(payload).read(8);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ping.prototype.getPayload = function() {
|
|
||||||
return this.nonce;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Ping = Message.COMMANDS.ping = Ping;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pong Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Pong
|
|
||||||
* @param{Buffer} nonce - a random 8 bytes buffer
|
|
||||||
*/
|
|
||||||
function Pong(nonce) {
|
|
||||||
this.command = 'pong';
|
|
||||||
this.nonce = nonce || CONNECTION_NONCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Pong, Ping);
|
|
||||||
module.exports.Pong = Message.COMMANDS.pong = Pong;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Addr Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Addressess
|
|
||||||
* @param{Array} addresses - array of know addresses
|
|
||||||
*/
|
|
||||||
function Addresses(addresses) {
|
|
||||||
this.command = 'addr';
|
|
||||||
this.addresses = addresses || [];
|
|
||||||
}
|
|
||||||
util.inherits(Addresses, Message);
|
|
||||||
|
|
||||||
Addresses.prototype.fromBuffer = function(payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
var addrCount = Math.min(parser.readVarintNum(), 1000);
|
|
||||||
|
|
||||||
this.addresses = [];
|
|
||||||
for (var i = 0; i < addrCount; i++) {
|
|
||||||
// TODO: Time actually depends on the version of the other peer (>=31402)
|
|
||||||
|
|
||||||
var time = parser.readUInt32LE();
|
|
||||||
var services = parser.readUInt64LEBN();
|
|
||||||
|
|
||||||
// parse the ipv6 to a string
|
|
||||||
var ipv6 = [];
|
|
||||||
for (var a = 0; a < 6; a++) {
|
|
||||||
ipv6.push(parser.read(2).toString('hex'));
|
|
||||||
}
|
|
||||||
ipv6 = ipv6.join(':');
|
|
||||||
|
|
||||||
// parse the ipv4 to a string
|
|
||||||
var ipv4 = [];
|
|
||||||
for (var b = 0; b < 4; b++) {
|
|
||||||
ipv4.push(parser.read(1)[0]);
|
|
||||||
}
|
|
||||||
ipv4 = ipv4.join('.');
|
|
||||||
|
|
||||||
var port = parser.readUInt16BE();
|
|
||||||
|
|
||||||
this.addresses.push({
|
|
||||||
time: time,
|
|
||||||
services: services,
|
|
||||||
ip: { v6: ipv6, v4: ipv4 },
|
|
||||||
port: port
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Addresses.prototype.getPayload = function() {
|
|
||||||
var put = new Put();
|
|
||||||
put.varint(this.addresses.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < this.addresses.length; i++) {
|
|
||||||
put.word32le(this.addresses[i].time);
|
|
||||||
put.word64le(this.addresses[i].services);
|
|
||||||
put.put(this.addresses[i].ip);
|
|
||||||
put.word16be(this.addresses[i].port);
|
|
||||||
}
|
|
||||||
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Addresses = Message.COMMANDS.addr = Addresses;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetAddr Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.GetAddresses
|
|
||||||
*/
|
|
||||||
function GetAddresses() {
|
|
||||||
this.command = 'getaddr';
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(GetAddresses, Message);
|
|
||||||
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verack Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.VerAck
|
|
||||||
*/
|
|
||||||
function VerAck() {
|
|
||||||
this.command = 'verack';
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(VerAck, Message);
|
|
||||||
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reject Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Reject
|
|
||||||
*/
|
|
||||||
function Reject() {
|
|
||||||
this.command = 'reject';
|
|
||||||
}
|
|
||||||
util.inherits(Reject, Message);
|
|
||||||
|
|
||||||
// TODO: Parse REJECT message
|
|
||||||
|
|
||||||
module.exports.Reject = Message.COMMANDS.reject = Reject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alert Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Alert
|
|
||||||
*/
|
|
||||||
function Alert(payload, signature) {
|
|
||||||
this.command = 'alert';
|
|
||||||
this.payload = payload || new Buffer(32);
|
|
||||||
this.signature = signature || new Buffer(32);
|
|
||||||
}
|
|
||||||
util.inherits(Alert, Message);
|
|
||||||
|
|
||||||
Alert.prototype.fromBuffer = function(payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
this.payload = parser.readVarintBuf(); // TODO: Use current format
|
|
||||||
this.signature = parser.readVarintBuf();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Alert.prototype.getPayload = function() {
|
|
||||||
var put = new Put();
|
|
||||||
put.varint(this.payload.length);
|
|
||||||
put.put(this.payload);
|
|
||||||
|
|
||||||
put.varint(this.signature.length);
|
|
||||||
put.put(this.signature);
|
|
||||||
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Alert = Message.COMMANDS.alert = Alert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Headers Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Headers
|
|
||||||
* @param{Array} blockheaders - array of block headers
|
|
||||||
*/
|
|
||||||
function Headers(blockheaders) {
|
|
||||||
this.command = 'headers';
|
|
||||||
this.headers = blockheaders || [];
|
|
||||||
}
|
|
||||||
util.inherits(Headers, Message);
|
|
||||||
|
|
||||||
Headers.prototype.fromBuffer = function(payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
var count = parser.readVarintNum();
|
|
||||||
|
|
||||||
this.headers = [];
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
var header = BlockHeaderModel._fromBufferReader(parser);
|
|
||||||
this.headers.push(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Headers.prototype.getPayload = function() {
|
|
||||||
var put = new Put();
|
|
||||||
put.varint(this.headers.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < this.headers.length; i++) {
|
|
||||||
var buffer = this.headers[i].toBuffer();
|
|
||||||
put.put(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Headers = Message.COMMANDS.headers = Headers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Block
|
|
||||||
* @param{Block} block
|
|
||||||
*/
|
|
||||||
function Block(block) {
|
|
||||||
this.command = 'block';
|
|
||||||
this.block = block;
|
|
||||||
}
|
|
||||||
util.inherits(Block, Message);
|
|
||||||
|
|
||||||
Block.prototype.fromBuffer = function(payload) {
|
|
||||||
this.block = BlockModel(payload);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Block.prototype.getPayload = function() {
|
|
||||||
return this.block.toBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Block = Message.COMMANDS.block = Block;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tx Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.Transaction
|
|
||||||
* @param{Transaction} transaction
|
|
||||||
*/
|
|
||||||
function Transaction(transaction) {
|
|
||||||
this.command = 'tx';
|
|
||||||
this.transaction = transaction;
|
|
||||||
}
|
|
||||||
util.inherits(Transaction, Message);
|
|
||||||
|
|
||||||
Transaction.prototype.fromBuffer = function(payload) {
|
|
||||||
this.transaction = TransactionModel(payload);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction.prototype.getPayload = function() {
|
|
||||||
return this.transaction.toBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Transaction = Message.COMMANDS.tx = Transaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getblocks Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.GetBlocks
|
|
||||||
* @param{Array} starts - array of buffers with the starting block hashes
|
|
||||||
* @param{Buffer} [stop] - hash of the last block
|
|
||||||
*/
|
|
||||||
function GetBlocks(starts, stop) {
|
|
||||||
this.command = 'getblocks';
|
|
||||||
this.version = PROTOCOL_VERSION;
|
|
||||||
this.starts = starts || [];
|
|
||||||
this.stop = stop || BufferUtil.NULL_HASH;
|
|
||||||
}
|
|
||||||
util.inherits(GetBlocks, Message);
|
|
||||||
|
|
||||||
GetBlocks.prototype.fromBuffer = function(payload) {
|
|
||||||
var parser = new BufferReader(payload);
|
|
||||||
this.version = parser.readUInt32LE();
|
|
||||||
|
|
||||||
var startCount = Math.min(parser.readVarintNum(), 500);
|
|
||||||
this.starts = [];
|
|
||||||
for (var i = 0; i < startCount; i++) {
|
|
||||||
this.starts.push(parser.read(32));
|
|
||||||
}
|
|
||||||
this.stop = parser.read(32);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
GetBlocks.prototype.getPayload = function() {
|
|
||||||
var put = new Put();
|
|
||||||
put.word32le(this.version);
|
|
||||||
put.varint(this.starts.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < this.starts.length; i++) {
|
|
||||||
if (this.starts[i].length !== 32) {
|
|
||||||
throw new Error('Invalid hash length');
|
|
||||||
}
|
|
||||||
put.put(this.starts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.stop.length !== 32) {
|
|
||||||
throw new Error('Invalid hash length');
|
|
||||||
}
|
|
||||||
put.put(this.stop);
|
|
||||||
|
|
||||||
return put.buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.GetBlocks = Message.COMMANDS.getblocks = GetBlocks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getheaders Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.GetHeaders
|
|
||||||
* @param{Array} starts - array of buffers with the starting block hashes
|
|
||||||
* @param{Buffer} [stop] - hash of the last block
|
|
||||||
*/
|
|
||||||
function GetHeaders(starts, stop) {
|
|
||||||
this.command = 'getheaders';
|
|
||||||
this.version = PROTOCOL_VERSION;
|
|
||||||
this.starts = starts || [];
|
|
||||||
this.stop = stop || BufferUtil.NULL_HASH;
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(GetHeaders, GetBlocks);
|
|
||||||
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetMempool Message
|
|
||||||
*
|
|
||||||
* @name Transport.Message.GetMempool
|
|
||||||
*/
|
|
||||||
function GetMempool() {
|
|
||||||
this.command = 'mempool';
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(GetMempool, Message);
|
|
||||||
module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool;
|
|
||||||
|
|
||||||
// TODO: Remove this PATCH (yemel)
|
|
||||||
Buffers.prototype.skip = function (i) {
|
|
||||||
if (i === 0) return;
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
|
@ -1,194 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var Buffers = require('buffers');
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
var Net = require('net');
|
|
||||||
var Socks5Client = require('socks5-client');
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Networks = require('../networks');
|
|
||||||
var Messages = require('./messages');
|
|
||||||
|
|
||||||
var MAX_RECEIVE_BUFFER = 10000000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Peer instance represents a remote bitcoin node and allows to communicate
|
|
||||||
* with it using the standar messages of the bitcoin p2p protocol.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```javascript
|
|
||||||
*
|
|
||||||
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050);
|
|
||||||
* peer.on('tx', function(tx) {
|
|
||||||
* console.log('New transaction: ', tx.id);
|
|
||||||
* });
|
|
||||||
* peer.connect();
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param {String} host - IP address of the remote host
|
|
||||||
* @param {Number} [port] - Port number of the remote host
|
|
||||||
* @param {Network} [network] - The context for this communication
|
|
||||||
* @returns {Peer} A new instance of Peer.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Peer(host, port, network) {
|
|
||||||
if (!(this instanceof Peer)) {
|
|
||||||
return new Peer(host, port, network);
|
|
||||||
}
|
|
||||||
|
|
||||||
// overloading stuff
|
|
||||||
if (port instanceof Object && !network) {
|
|
||||||
network = port;
|
|
||||||
port = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.host = host;
|
|
||||||
this.status = Peer.STATUS.DISCONNECTED;
|
|
||||||
this.network = network || Networks.livenet;
|
|
||||||
this.port = port || this.network.port;
|
|
||||||
|
|
||||||
this.dataBuffer = new Buffers();
|
|
||||||
|
|
||||||
this.version = 0;
|
|
||||||
this.bestHeight = 0;
|
|
||||||
this.subversion = null;
|
|
||||||
|
|
||||||
// set message handlers
|
|
||||||
var self = this;
|
|
||||||
this.on('verack', function() {
|
|
||||||
self.status = Peer.STATUS.READY;
|
|
||||||
self.emit('ready');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('version', function(message) {
|
|
||||||
self.version = message.version;
|
|
||||||
self.subversion = message.subversion;
|
|
||||||
self.bestHeight = message.start_height
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('ping', function(message) {
|
|
||||||
self._sendPong(message.nonce);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
util.inherits(Peer, EventEmitter);
|
|
||||||
|
|
||||||
Peer.STATUS = {
|
|
||||||
DISCONNECTED: 'disconnected',
|
|
||||||
CONNECTING: 'connecting',
|
|
||||||
CONNECTED: 'connected',
|
|
||||||
READY: 'ready'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a socks5 proxy for the connection. Enables the use of the TOR network.
|
|
||||||
*
|
|
||||||
* @param {String} host - IP address of the proxy
|
|
||||||
* @param {Number} port - Port number of the proxy
|
|
||||||
* @returns {Peer} The same Peer instance.
|
|
||||||
*/
|
|
||||||
Peer.prototype.setProxy = function(host, port) {
|
|
||||||
if (this.status != Peer.STATUS.DISCONNECTED) {
|
|
||||||
throw Error('Invalid State');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.proxy = {
|
|
||||||
host: host,
|
|
||||||
port: port
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init the connection with the remote peer.
|
|
||||||
*
|
|
||||||
* @returns {Socket} The same peer instance.
|
|
||||||
*/
|
|
||||||
Peer.prototype.connect = function() {
|
|
||||||
this.socket = this._getSocket();
|
|
||||||
this.status = Peer.STATUS.CONNECTING;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
this.socket.on('connect', function(ev) {
|
|
||||||
self.status = Peer.STATUS.CONNECTED;
|
|
||||||
self.emit('connect');
|
|
||||||
self._sendVersion();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('error', self.disconnect.bind(this));
|
|
||||||
this.socket.on('end', self.disconnect.bind(this));
|
|
||||||
|
|
||||||
this.socket.on('data', function(data) {
|
|
||||||
self.dataBuffer.push(data);
|
|
||||||
|
|
||||||
if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) return self.disconnect();
|
|
||||||
self._readMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.connect(this.port, this.host);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the remote connection.
|
|
||||||
*
|
|
||||||
* @returns {Socket} The same peer instance.
|
|
||||||
*/
|
|
||||||
Peer.prototype.disconnect = function() {
|
|
||||||
this.status = Peer.STATUS.DISCONNECTED;
|
|
||||||
this.socket.destroy();
|
|
||||||
this.emit('disconnect');
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a Message to the remote peer.
|
|
||||||
*
|
|
||||||
* @param {Message} message - A message instance
|
|
||||||
*/
|
|
||||||
Peer.prototype.sendMessage = function(message) {
|
|
||||||
this.socket.write(message.serialize(this.network));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function that sends VERSION message to the remote peer.
|
|
||||||
*/
|
|
||||||
Peer.prototype._sendVersion = function() {
|
|
||||||
var message = new Messages.Version();
|
|
||||||
this.sendMessage(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a PONG message to the remote peer.
|
|
||||||
*/
|
|
||||||
Peer.prototype._sendPong = function(nonce) {
|
|
||||||
var message = new Messages.Pong(nonce);
|
|
||||||
this.sendMessage(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function that tries to read a message from the data buffer
|
|
||||||
*/
|
|
||||||
Peer.prototype._readMessage = function() {
|
|
||||||
var message = Messages.parseMessage(this.network, this.dataBuffer);
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
this.emit(message.command, message);
|
|
||||||
this._readMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function that creates a socket using a proxy if neccesary.
|
|
||||||
*
|
|
||||||
* @returns {Socket} A Socket instance not yet connected.
|
|
||||||
*/
|
|
||||||
Peer.prototype._getSocket = function() {
|
|
||||||
if (this.proxy) {
|
|
||||||
return new Socks5Client(this.proxy.host, this.proxy.port);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Net.Socket();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Peer;
|
|
|
@ -1,275 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var dns = require('dns');
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
var Networks = require('../networks');
|
|
||||||
var sha256 = require('../crypto/hash').sha256;
|
|
||||||
var Peer = require('./peer');
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
function now() {
|
|
||||||
return Math.floor(new Date().getTime() / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pool is a collection of Peers. A pool will discover peers from DNS seeds, and
|
|
||||||
* collect information about new peers in the network. When a peer disconnects the pool
|
|
||||||
* will connect to others that are available to maintain a max number of
|
|
||||||
* ongoing peer connections. Peer events are relayed to the pool.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```javascript
|
|
||||||
*
|
|
||||||
* var pool = new Pool(Networks.livenet);
|
|
||||||
* pool.on('peerinv', function(peer, message) {
|
|
||||||
* // do something with the inventory announcement
|
|
||||||
* });
|
|
||||||
* pool.connect();
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param {Network|String} network - The network to connect
|
|
||||||
* @returns {Pool}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Pool(network) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.network = Networks.get(network) || Networks.defaultNetwork;
|
|
||||||
this.keepalive = false;
|
|
||||||
this._connectedPeers = {};
|
|
||||||
this._addrs = [];
|
|
||||||
|
|
||||||
this.on('peeraddr', function peerAddrEvent(peer, message) {
|
|
||||||
var addrs = message.addresses;
|
|
||||||
var length = addrs.length;
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
var addr = addrs[i];
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
this._addAddr(addr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('seed', function seedEvent(ips) {
|
|
||||||
ips.forEach(function(ip) {
|
|
||||||
self._addAddr({
|
|
||||||
ip: {
|
|
||||||
v4: ip
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (self.keepalive) {
|
|
||||||
self._fillConnections();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('peerdisconnect', function peerDisconnectEvent(peer, addr) {
|
|
||||||
self._deprioritizeAddr(addr);
|
|
||||||
self._removeConnectedPeer(addr);
|
|
||||||
if (self.keepalive) {
|
|
||||||
self._fillConnections();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Pool, EventEmitter);
|
|
||||||
|
|
||||||
Pool.MaxConnectedPeers = 8;
|
|
||||||
Pool.RetrySeconds = 30;
|
|
||||||
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr',
|
|
||||||
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block',
|
|
||||||
'tx', 'getblocks', 'getheaders'
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will initiatiate connection to peers, if available peers have been added to
|
|
||||||
* the pool, it will connect to those, otherwise will use DNS seeds to find
|
|
||||||
* peers to connect. When a peer disconnects it will add another.
|
|
||||||
*/
|
|
||||||
Pool.prototype.connect = function connect() {
|
|
||||||
this.keepalive = true;
|
|
||||||
var self = this;
|
|
||||||
if (self._addrs.length === 0) {
|
|
||||||
self._addAddrsFromSeeds();
|
|
||||||
} else {
|
|
||||||
self._fillConnections();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will disconnect all peers that are connected.
|
|
||||||
*/
|
|
||||||
Pool.prototype.disconnect = function disconnect() {
|
|
||||||
this.keepalive = false;
|
|
||||||
for (var i in this._connectedPeers) {
|
|
||||||
this._connectedPeers[i].disconnect();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Number} The number of peers currently connected.
|
|
||||||
*/
|
|
||||||
Pool.prototype.numberConnected = function numberConnected() {
|
|
||||||
return Object.keys(this._connectedPeers).length;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will fill the conneted peers to the maximum amount.
|
|
||||||
*/
|
|
||||||
Pool.prototype._fillConnections = function _fillConnections() {
|
|
||||||
var length = this._addrs.length;
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
if (this.numberConnected() >= Pool.MaxConnectedPeers) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var addr = this._addrs[i];
|
|
||||||
if (!addr.retryTime || now() > addr.retryTime) {
|
|
||||||
this._connectPeer(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will remove a peer from the list of connected peers.
|
|
||||||
* @param {Object} addr - An addr from the list of addrs
|
|
||||||
*/
|
|
||||||
Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) {
|
|
||||||
if (this._connectedPeers[addr.hash].status !== Peer.STATUS.DISCONNECTED) {
|
|
||||||
this._connectedPeers[addr.hash].disconnect();
|
|
||||||
} else {
|
|
||||||
delete this._connectedPeers[addr.hash];
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will connect a peer and add to the list of connected peers.
|
|
||||||
* @param {Object} addr - An addr from the list of addrs
|
|
||||||
*/
|
|
||||||
Pool.prototype._connectPeer = function _connectPeer(addr) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
function addConnectedPeer(addr) {
|
|
||||||
var port = addr.port || self.network.port;
|
|
||||||
var ip = addr.ip.v4 || addr.ip.v6;
|
|
||||||
var peer = new Peer(ip, port, self.network);
|
|
||||||
peer.on('disconnect', function peerDisconnect() {
|
|
||||||
self.emit('peerdisconnect', peer, addr);
|
|
||||||
});
|
|
||||||
peer.on('ready', function peerReady() {
|
|
||||||
self.emit('peerready', peer, addr);
|
|
||||||
});
|
|
||||||
Pool.PeerEvents.forEach(function addPeerEvents(event) {
|
|
||||||
peer.on(event, function peerEvent(message) {
|
|
||||||
self.emit('peer' + event, peer, message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
peer.connect();
|
|
||||||
self._connectedPeers[addr.hash] = peer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._connectedPeers[addr.hash]) {
|
|
||||||
addConnectedPeer(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will deprioritize an addr in the list of addrs by moving it to the end
|
|
||||||
* of the array, and setting a retryTime
|
|
||||||
* @param {Object} addr - An addr from the list of addrs
|
|
||||||
*/
|
|
||||||
Pool.prototype._deprioritizeAddr = function _deprioritizeAddr(addr) {
|
|
||||||
for (var i = 0; i < this._addrs.length; i++) {
|
|
||||||
if (this._addrs[i].hash === addr.hash) {
|
|
||||||
var middle = this._addrs[i];
|
|
||||||
middle.retryTime = now() + Pool.RetrySeconds;
|
|
||||||
var beginning = this._addrs.splice(0, i);
|
|
||||||
var end = this._addrs.splice(i + 1, this._addrs.length);
|
|
||||||
var combined = beginning.concat(end);
|
|
||||||
this._addrs = combined.concat([middle]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add an addr to the beginning of the addrs array
|
|
||||||
* @param {Object}
|
|
||||||
*/
|
|
||||||
Pool.prototype._addAddr = function _addAddr(addr) {
|
|
||||||
|
|
||||||
// make a unique key
|
|
||||||
addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex');
|
|
||||||
|
|
||||||
var length = this._addrs.length;
|
|
||||||
var exists = false;
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
if (this._addrs[i].hash === addr.hash) {
|
|
||||||
exists = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!exists) {
|
|
||||||
this._addrs.unshift(addr);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add addrs to the list of addrs from a DNS seed
|
|
||||||
* @param {String} seed - A domain name to resolve known peers
|
|
||||||
* @param {Function} done
|
|
||||||
*/
|
|
||||||
Pool.prototype._addAddrsFromSeed = function _addAddrsFromSeed(seed) {
|
|
||||||
var self = this;
|
|
||||||
dns.resolve(seed, function(err, ips) {
|
|
||||||
if (err) {
|
|
||||||
self.emit('seederror', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!ips || !ips.length) {
|
|
||||||
self.emit('seederror', new Error('No IPs found from seed lookup.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// announce to pool
|
|
||||||
self.emit('seed', ips);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add addrs to the list of addrs from network DNS seeds
|
|
||||||
* @param {Function} done
|
|
||||||
*/
|
|
||||||
Pool.prototype._addAddrsFromSeeds = function _addAddrsFromSeeds() {
|
|
||||||
var self = this;
|
|
||||||
var seeds = this.network.dnsSeeds;
|
|
||||||
seeds.forEach(function(seed) {
|
|
||||||
self._addAddrsFromSeed(seed);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {String} A string formatted for the console
|
|
||||||
*/
|
|
||||||
Pool.prototype.inspect = function inspect() {
|
|
||||||
return '<Pool network: ' +
|
|
||||||
this.network + ', connected: ' +
|
|
||||||
this.numberConnected() + ', available: ' +
|
|
||||||
this._addrs.length + '>';
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Pool;
|
|
Loading…
Reference in New Issue